feat(modules): show permissions before installation

This commit is contained in:
2026-08-07 01:59:35 +02:00
parent 9d522ef6e0
commit bc589a3d4f
6 changed files with 61 additions and 1 deletions
@@ -411,6 +411,17 @@ class ModulePackageArtifactIdentity(BaseModel):
source_commit: str | None = None 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): class ModulePackageCatalogItem(BaseModel):
module_id: str module_id: str
name: str name: str
@@ -423,6 +434,7 @@ class ModulePackageCatalogItem(BaseModel):
availability: Literal["available", "withdrawn"] = "available" availability: Literal["available", "withdrawn"] = "available"
availability_reason: str | None = None availability_reason: str | None = None
configuration_requirements: list[str] = Field(default_factory=list) configuration_requirements: list[str] = Field(default_factory=list)
permissions: list[ModulePackagePermissionItem] = Field(default_factory=list)
release_notes_url: str | None = None release_notes_url: str | None = None
source: ModulePackageCatalogSource | None = None source: ModulePackageCatalogSource | None = None
artifact_integrity: dict[str, ModulePackageArtifactIdentity] = Field(default_factory=dict) artifact_integrity: dict[str, ModulePackageArtifactIdentity] = Field(default_factory=dict)
+1 -1
View File
@@ -147,7 +147,7 @@ manifest = ModuleManifest(
summary="Move a reviewed module package plan through preflight, maintenance-gated queueing, daemon execution, and durable run evidence.", summary="Move a reviewed module package plan through preflight, maintenance-gated queueing, daemon execution, and durable run evidence.",
body=( 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. " "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. " "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." "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."
), ),
+11
View File
@@ -141,6 +141,16 @@ class CatalogPlanItemTests(unittest.TestCase):
"name": "Calendar", "name": "Calendar",
"version": "0.2.0", "version": "0.2.0",
"action": "install", "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"], "dependencies": ["access"],
"current_version_min": "0.1.5", "current_version_min": "0.1.5",
"current_version_max_exclusive": "0.2.0", "current_version_max_exclusive": "0.2.0",
@@ -169,6 +179,7 @@ class CatalogPlanItemTests(unittest.TestCase):
self.assertFalse(item.plan_allowed) self.assertFalse(item.plan_allowed)
self.assertEqual("blocked", item.catalog_state) self.assertEqual("blocked", item.catalog_state)
self.assertTrue(item.update_available) self.assertTrue(item.update_available)
self.assertEqual("calendar:event:read", item.permissions[0].scope)
self.assertEqual(3, len(item.compatibility_reasons)) self.assertEqual(3, len(item.compatibility_reasons))
self.assertTrue(any("update window" in reason for reason in 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)) self.assertTrue(any("Required module access" in reason for reason in item.compatibility_reasons))
+12
View File
@@ -443,6 +443,17 @@ export type ModulePackageArtifactIdentity = {
source_commit?: string | null; 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 = { export type ModulePackageCatalogItem = {
module_id: string; module_id: string;
name: string; name: string;
@@ -455,6 +466,7 @@ export type ModulePackageCatalogItem = {
availability: "available" | "withdrawn"; availability: "available" | "withdrawn";
availability_reason?: string | null; availability_reason?: string | null;
configuration_requirements: string[]; configuration_requirements: string[];
permissions: ModulePackagePermissionItem[];
release_notes_url?: string | null; release_notes_url?: string | null;
source?: ModulePackageCatalogSource | null; source?: ModulePackageCatalogSource | null;
artifact_integrity: Record<string, ModulePackageArtifactIdentity>; artifact_integrity: Record<string, ModulePackageArtifactIdentity>;
@@ -580,6 +580,16 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
</div>)} </div>)}
{item.description && <p className="module-package-catalog-description">{item.description}</p>} {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.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.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.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>} {item.recovery_notes && <p className="module-package-catalog-description">{item.recovery_notes}</p>}
@@ -954,6 +964,17 @@ function requiredInterfacesLabel(item: ModulePackageCatalogItem): string {
}).join(", "); }).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 { function migrationSafetyLabel(value: ModulePackageCatalogItem["migration_safety"] | ModuleInstallTargetItem["migration_safety"]): string {
if (value === "requires_review") return "i18n:govoplan-admin.requires_review.8ba0b8c6"; if (value === "requires_review") return "i18n:govoplan-admin.requires_review.8ba0b8c6";
if (value === "forward_only") return "i18n:govoplan-admin.forward_only.6c107a46"; if (value === "forward_only") return "i18n:govoplan-admin.forward_only.6c107a46";
+4
View File
@@ -16,6 +16,8 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-admin.release_notes.7c8f1012": "Release notes", "i18n:govoplan-admin.release_notes.7c8f1012": "Release notes",
"i18n:govoplan-admin.withdrawn.7c8f1013": "Withdrawn", "i18n:govoplan-admin.withdrawn.7c8f1013": "Withdrawn",
"i18n:govoplan-admin.update_available.7c8f1014": "Update available", "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.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.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.", "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.release_notes.7c8f1012": "Versionshinweise",
"i18n:govoplan-admin.withdrawn.7c8f1013": "Zurückgezogen", "i18n:govoplan-admin.withdrawn.7c8f1013": "Zurückgezogen",
"i18n:govoplan-admin.update_available.7c8f1014": "Aktualisierung verfügbar", "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.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.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.", "i18n:govoplan-admin.system_administration_write_permission_is_required.6bf3c003": "Eine Schreibberechtigung für die Systemadministration ist erforderlich.",