feat(release): disclose module permissions in catalog

This commit is contained in:
2026-08-07 01:59:18 +02:00
parent 78811f7f6e
commit dce725636d
3 changed files with 48 additions and 1 deletions
+5
View File
@@ -217,6 +217,11 @@ requirements. Missing dependency/interface providers and unsupported update
windows are surfaced before an operator adds the entry to a plan; installer
preflight remains authoritative.
Catalog entries also carry the permission definitions declared by the tagged
module manifest. Admin groups and exposes their scopes before an install or
update is planned. This is disclosure only: installing a module does not grant
its permissions to an account, role, group, tenant, or service account.
Package lifecycle and availability are intentionally separate:
- install, update, and uninstall change the instance-wide package composition;
+26 -1
View File
@@ -5,7 +5,7 @@ import tomllib
import unittest
from pathlib import Path
from govoplan_core.core.modules import ModuleManifest
from govoplan_core.core.modules import ModuleManifest, PermissionDefinition
from govoplan_core.core.provider_governance import (
ExternalProviderDeclaration,
ModuleArchitectureDeclaration,
@@ -88,6 +88,18 @@ class ReleaseCatalogEntrySynthesisTests(unittest.TestCase):
id="example",
name="Example",
version="1.2.3",
permissions=(
PermissionDefinition(
scope="example:records:read",
label="Read records",
description="Read example records.",
category="Records",
level="tenant",
module_id="example",
resource="records",
action="read",
),
),
architecture=architecture,
external_providers=(provider,),
),
@@ -112,6 +124,19 @@ class ReleaseCatalogEntrySynthesisTests(unittest.TestCase):
"default_authority_mode"
],
)
self.assertEqual(
{
"scope": "example:records:read",
"label": "Read records",
"description": "Read example records.",
"category": "Records",
"level": "tenant",
"resource": "records",
"action": "read",
"deprecated": False,
},
entry["permissions"][0],
)
def test_selective_update_synthesizes_initial_entries_from_package_manifests(self) -> None:
payload: dict[str, object] = {
@@ -172,6 +172,23 @@ def manifest_catalog_entry(
entry["dependencies"] = list(manifest.dependencies)
if manifest.optional_dependencies:
entry["optional_dependencies"] = list(manifest.optional_dependencies)
if manifest.permissions:
entry["permissions"] = [
{
"scope": permission.scope,
"label": permission.label,
"description": permission.description,
"category": permission.category,
"level": permission.level,
"resource": permission.resource,
"action": permission.action,
"deprecated": permission.deprecated,
}
for permission in sorted(
manifest.permissions,
key=lambda item: item.scope,
)
]
if manifest.architecture is not None:
entry["architecture"] = manifest.architecture.to_dict()
entry["information_governance"] = manifest.information_governance.to_dict()