Files
yidaima/RuoYi-Vue/docs/superpowers/specs/2026-07-11-plugin-permission-target-executor-design.md

98 lines
5.8 KiB
Markdown
Raw Permalink Normal View History

# Plugin Permission Target Executor Design
## Context
P2-D8e2d can route each contribution to a frozen Executor and persist approval evidence, but only `sql-jdbc:v1` changes a real target. Permission contributions still use `dry-run:v1`; configuring the reserved permission route to any other code fails because no adapter is installed.
P2-D8e2e introduces `permission-jdbc:v1` for RuoYi-compatible target databases. It consumes a narrow structured document, creates one Plugin-owned button permission, grants it to named roles, records target ownership, and safely compensates only the resources it owns.
## Target And Isolation
The adapter reuses the existing isolated JDBC target configuration and connection factory:
- `factory.plugin-execution.sql-target` supplies environment, catalog, connection, and lease settings;
- `factory_plugin_target_receipt` remains the durable per-step lease and idempotency ledger;
- the target must be separate from the Factory control database;
- `sys_menu`, `sys_role`, and `sys_role_menu` must be RuoYi-compatible;
- permission ownership tables must be installed by the updated target bootstrap script.
The adapter never executes SQL supplied by a Plugin. All statements are fixed prepared statements owned by the platform.
## Permission Document
Each `PERMISSION + backend` contribution contains exactly one JSON document:
```json
{
"schemaVersion": "1.0",
"permissionCode": "generator:delivery:verify",
"displayName": "交付验证",
"parentPermissionCode": "generator:plugin:list",
"roleKeys": ["common"]
}
```
Validation is strict:
- only the five published fields are accepted;
- `schemaVersion` must be `1.0`;
- `permissionCode` must equal the immutable contribution key and use a three-part lowercase permission identifier;
- wildcard permission identifiers are rejected;
- `parentPermissionCode` must identify a different existing active menu;
- display name is non-blank and at most 50 characters;
- one to 20 unique lowercase role keys are allowed;
- the document is bounded to 64 KiB.
Role keys are sorted before computing a semantic document fingerprint. Apply and rollback resources may differ in whitespace or role ordering, but must describe the same semantic permission identity.
## Apply Semantics
After claiming the generic target receipt lease, the adapter runs one database transaction:
1. Resolve exactly one active parent menu by `parentPermissionCode`.
2. Resolve exactly one active `sys_role` row for every role key.
3. Lock the ownership row for `permissionCode`.
4. Reject an existing unowned `sys_menu.perms` collision.
5. Create a button (`menu_type = 'F'`) with a generated target-local `menu_id`.
6. Persist the Plugin owner, contribution key, semantic fingerprint, parent identity, generated menu ID, and `ACTIVE` status.
7. Insert only the declared `sys_role_menu` grants and matching ownership rows.
An interrupted attempt can be reclaimed through the existing lease. If the ownership row is already `ACTIVE`, every immutable identity, menu field, owned role grant, and role ID must still match before the attempt can complete. Drift is rejected rather than overwritten.
## Compensation Semantics
Compensation locks and validates the ownership row before deletion. It refuses to continue when:
- the permission is not owned by the same Plugin and contribution key;
- the semantic document fingerprint differs;
- the target menu or an owned role grant has drifted;
- another role was granted the Plugin-owned menu outside the ownership ledger;
- the button unexpectedly has child menus.
When validation succeeds, one transaction deletes the owned role grants and button menu, removes active grant rows, and marks the permission ownership row `REMOVED`. The tombstone makes an interrupted compensation retry idempotent and preserves audit identity. A later apply by the same Plugin/contribution key may reactivate the permission with a new target menu ID after the prior version has been compensated.
## Receipts And Failures
The external receipt is deterministic: `permission:<environment>:<step-idempotency-key>`. Duplicate `SUCCEEDED` delivery returns the stored receipt without touching target authorization rows. Active leases cannot be stolen; failed or expired attempts can be reclaimed.
All target errors are sanitized. JDBC URLs, credentials, SQL details, role IDs, and driver exceptions are not returned through control-side execution history.
## First Delivery Plugin
`delivery.preview-permission@1.0.0` publishes the reviewed `generator:delivery:verify` permission document and its compensation resource. The rehearsal target supplies an existing `generator:plugin:list` parent and `common` role, then proves publish-plan resolution, apply, duplicate delivery, role authorization, compensation, and duplicate compensation against H2 in MySQL mode.
## Probe And UI
When `permission-jdbc:v1` is selected by the default or route map, the existing manual JDBC target probe additionally checks both permission ownership tables. A missing permission schema has its own non-secret state and blocks a READY result. The Plugin page recognizes that state and treats both SQL and permission JDBC adapters as requiring the same isolated target readiness.
## Compatibility And Scope
- Default routing stays `dry-run:v1`.
- SQL-only installations do not require permission ownership tables unless `permission-jdbc:v1` is configured.
- Existing SQL receipts and SQL Executor behavior are unchanged.
- This stage does not create directory/menu pages, backend endpoints, users, roles, or arbitrary permission hierarchies.
- It does not adopt pre-existing permission rows or delete unmanaged role grants.
- Multi-person approval, external tickets, permission cache invalidation across a running target cluster, and the real admin-menu adapter remain later work.
- Plaintext development credentials and TLS verification remain deferred by explicit user decision.