feat(modules): show permissions before installation
This commit is contained in:
@@ -411,6 +411,17 @@ class ModulePackageArtifactIdentity(BaseModel):
|
||||
source_commit: str | None = None
|
||||
|
||||
|
||||
class ModulePackagePermissionItem(BaseModel):
|
||||
scope: str
|
||||
label: str
|
||||
description: str
|
||||
category: str
|
||||
level: Literal["system", "tenant"]
|
||||
resource: str
|
||||
action: str
|
||||
deprecated: bool = False
|
||||
|
||||
|
||||
class ModulePackageCatalogItem(BaseModel):
|
||||
module_id: str
|
||||
name: str
|
||||
@@ -423,6 +434,7 @@ class ModulePackageCatalogItem(BaseModel):
|
||||
availability: Literal["available", "withdrawn"] = "available"
|
||||
availability_reason: str | None = None
|
||||
configuration_requirements: list[str] = Field(default_factory=list)
|
||||
permissions: list[ModulePackagePermissionItem] = Field(default_factory=list)
|
||||
release_notes_url: str | None = None
|
||||
source: ModulePackageCatalogSource | None = None
|
||||
artifact_integrity: dict[str, ModulePackageArtifactIdentity] = Field(default_factory=dict)
|
||||
|
||||
@@ -147,7 +147,7 @@ manifest = ModuleManifest(
|
||||
summary="Move a reviewed module package plan through preflight, maintenance-gated queueing, daemon execution, and durable run evidence.",
|
||||
body=(
|
||||
"The Modules administration surface projects one operator workflow: save a package plan, resolve preflight findings, enter maintenance mode with the required authority, queue a supervised installer request, and inspect the matching run record. "
|
||||
"When no deployment-specific catalog is configured, the package directory discovers the signed public GovOPlaN stable catalog. Operators can search and filter available, installed, update, blocked, and withdrawn entries; each row exposes the signed source revision, artifact digest, release notes, and configuration requirements supplied by the catalog. Missing dependencies, incompatible named interfaces, unsupported update windows, and withdrawn releases block plan creation. Selecting an eligible entry copies its exact signed registry identities into the plan; artifact download and digest verification happen only in the trusted installer. "
|
||||
"When no deployment-specific catalog is configured, the package directory discovers the signed public GovOPlaN stable catalog. Operators can search and filter available, installed, update, blocked, and withdrawn entries; each row exposes the signed source revision, artifact digest, release notes, configuration requirements, and manifest-declared permission scopes supplied by the catalog. Permission disclosure supports review but never grants a scope. Missing dependencies, incompatible named interfaces, unsupported update windows, and withdrawn releases block plan creation. Selecting an eligible entry copies its exact signed registry identities into the plan; artifact download and digest verification happen only in the trusted installer. "
|
||||
"The stage indicator is derived from the saved plan timestamp, the latest matching request, and its run; an older request is never presented as evidence for a newer plan. "
|
||||
"Disabled queue actions name the earliest blocker, the person who can resolve it, and the plan surface where work continues. Package mutation remains outside the FastAPI process and recovery evidence remains durable in the installer ledger. Shared deployments require an immutable image rollout rather than node-local mutation. Tenant entitlement and user/View visibility remain policy settings, not package lifecycle operations."
|
||||
),
|
||||
|
||||
@@ -141,6 +141,16 @@ class CatalogPlanItemTests(unittest.TestCase):
|
||||
"name": "Calendar",
|
||||
"version": "0.2.0",
|
||||
"action": "install",
|
||||
"permissions": [{
|
||||
"scope": "calendar:event:read",
|
||||
"label": "Read events",
|
||||
"description": "Read calendar events.",
|
||||
"category": "Calendar",
|
||||
"level": "tenant",
|
||||
"resource": "event",
|
||||
"action": "read",
|
||||
"deprecated": False,
|
||||
}],
|
||||
"dependencies": ["access"],
|
||||
"current_version_min": "0.1.5",
|
||||
"current_version_max_exclusive": "0.2.0",
|
||||
@@ -169,6 +179,7 @@ class CatalogPlanItemTests(unittest.TestCase):
|
||||
self.assertFalse(item.plan_allowed)
|
||||
self.assertEqual("blocked", item.catalog_state)
|
||||
self.assertTrue(item.update_available)
|
||||
self.assertEqual("calendar:event:read", item.permissions[0].scope)
|
||||
self.assertEqual(3, len(item.compatibility_reasons))
|
||||
self.assertTrue(any("update window" in reason for reason in item.compatibility_reasons))
|
||||
self.assertTrue(any("Required module access" in reason for reason in item.compatibility_reasons))
|
||||
|
||||
@@ -443,6 +443,17 @@ export type ModulePackageArtifactIdentity = {
|
||||
source_commit?: string | null;
|
||||
};
|
||||
|
||||
export type ModulePackagePermissionItem = {
|
||||
scope: string;
|
||||
label: string;
|
||||
description: string;
|
||||
category: string;
|
||||
level: "system" | "tenant";
|
||||
resource: string;
|
||||
action: string;
|
||||
deprecated: boolean;
|
||||
};
|
||||
|
||||
export type ModulePackageCatalogItem = {
|
||||
module_id: string;
|
||||
name: string;
|
||||
@@ -455,6 +466,7 @@ export type ModulePackageCatalogItem = {
|
||||
availability: "available" | "withdrawn";
|
||||
availability_reason?: string | null;
|
||||
configuration_requirements: string[];
|
||||
permissions: ModulePackagePermissionItem[];
|
||||
release_notes_url?: string | null;
|
||||
source?: ModulePackageCatalogSource | null;
|
||||
artifact_integrity: Record<string, ModulePackageArtifactIdentity>;
|
||||
|
||||
@@ -580,6 +580,16 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
</div>)}
|
||||
{item.description && <p className="module-package-catalog-description">{item.description}</p>}
|
||||
{item.configuration_requirements.length > 0 && <p className="module-package-catalog-description">i18n:govoplan-admin.configuration_requirements.7c8f1011 {item.configuration_requirements.join(", ")}</p>}
|
||||
{item.permissions.length > 0 && <details className="module-package-catalog-description">
|
||||
<summary>{i18nMessage("i18n:govoplan-admin.declared_permissions.7c8f1015", { value0: item.permissions.length })}</summary>
|
||||
<p className="muted small-note">i18n:govoplan-admin.installing_does_not_grant_permissions.7c8f1016</p>
|
||||
<div className="module-management-details">
|
||||
{permissionCategoryLabels(item).map((label) => <span key={label}>{label}</span>)}
|
||||
</div>
|
||||
<div className="module-management-details">
|
||||
{item.permissions.map((permission) => <span key={permission.scope} title={permission.description}><code>{permission.scope}</code> {permission.label}{permission.deprecated ? " (deprecated)" : ""}</span>)}
|
||||
</div>
|
||||
</details>}
|
||||
{item.migration_notes && <p className="module-package-catalog-description">{item.migration_notes}</p>}
|
||||
{item.bridge_notes && <p className="module-package-catalog-description">{item.bridge_notes}</p>}
|
||||
{item.recovery_notes && <p className="module-package-catalog-description">{item.recovery_notes}</p>}
|
||||
@@ -954,6 +964,17 @@ function requiredInterfacesLabel(item: ModulePackageCatalogItem): string {
|
||||
}).join(", ");
|
||||
}
|
||||
|
||||
function permissionCategoryLabels(item: ModulePackageCatalogItem): string[] {
|
||||
const counts = new Map<string, number>();
|
||||
item.permissions.forEach((permission) => {
|
||||
const key = `${permission.category} · ${permission.level}`;
|
||||
counts.set(key, (counts.get(key) ?? 0) + 1);
|
||||
});
|
||||
return [...counts.entries()]
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.map(([key, count]) => `${key}: ${count}`);
|
||||
}
|
||||
|
||||
function migrationSafetyLabel(value: ModulePackageCatalogItem["migration_safety"] | ModuleInstallTargetItem["migration_safety"]): string {
|
||||
if (value === "requires_review") return "i18n:govoplan-admin.requires_review.8ba0b8c6";
|
||||
if (value === "forward_only") return "i18n:govoplan-admin.forward_only.6c107a46";
|
||||
|
||||
@@ -16,6 +16,8 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.release_notes.7c8f1012": "Release notes",
|
||||
"i18n:govoplan-admin.withdrawn.7c8f1013": "Withdrawn",
|
||||
"i18n:govoplan-admin.update_available.7c8f1014": "Update available",
|
||||
"i18n:govoplan-admin.declared_permissions.7c8f1015": "Declared permissions ({value0})",
|
||||
"i18n:govoplan-admin.installing_does_not_grant_permissions.7c8f1016": "Installing this module does not grant these permissions.",
|
||||
"i18n:govoplan-admin.administration_data_is_loading.6bf3c001": "Administration data is loading.",
|
||||
"i18n:govoplan-admin.an_administration_operation_is_in_progress.6bf3c002": "An administration operation is in progress.",
|
||||
"i18n:govoplan-admin.system_administration_write_permission_is_required.6bf3c003": "System administration write permission is required.",
|
||||
@@ -466,6 +468,8 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.release_notes.7c8f1012": "Versionshinweise",
|
||||
"i18n:govoplan-admin.withdrawn.7c8f1013": "Zurückgezogen",
|
||||
"i18n:govoplan-admin.update_available.7c8f1014": "Aktualisierung verfügbar",
|
||||
"i18n:govoplan-admin.declared_permissions.7c8f1015": "Deklarierte Berechtigungen ({value0})",
|
||||
"i18n:govoplan-admin.installing_does_not_grant_permissions.7c8f1016": "Die Installation dieses Moduls vergibt diese Berechtigungen nicht.",
|
||||
"i18n:govoplan-admin.administration_data_is_loading.6bf3c001": "Administrationsdaten werden geladen.",
|
||||
"i18n:govoplan-admin.an_administration_operation_is_in_progress.6bf3c002": "Eine Administrationsaktion wird gerade ausgeführt.",
|
||||
"i18n:govoplan-admin.system_administration_write_permission_is_required.6bf3c003": "Eine Schreibberechtigung für die Systemadministration ist erforderlich.",
|
||||
|
||||
Reference in New Issue
Block a user