feat(core): require scoped user workflows
This commit is contained in:
@@ -237,6 +237,35 @@ class DocumentationTopic:
|
||||
metadata: Mapping[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
def user_workflow_scope_condition_issues(topic: DocumentationTopic) -> tuple[str, ...]:
|
||||
"""Return fail-closed authoring issues for a user-facing workflow topic.
|
||||
|
||||
Topic conditions are alternatives. Every alternative therefore needs an
|
||||
explicit scope constraint; otherwise one unscoped alternative would make
|
||||
the workflow visible regardless of the actor's task authority.
|
||||
"""
|
||||
|
||||
raw_kind = topic.metadata.get("kind")
|
||||
kind = raw_kind.strip().lower().replace("_", "-") if isinstance(raw_kind, str) else ""
|
||||
if kind != "workflow" or "user" not in topic.documentation_types:
|
||||
return ()
|
||||
if not topic.conditions:
|
||||
return ("user workflow topics must declare at least one scope-conditioned alternative",)
|
||||
|
||||
unscoped_alternatives = tuple(
|
||||
index
|
||||
for index, condition in enumerate(topic.conditions, start=1)
|
||||
if not any(scope.strip() for scope in (*condition.required_scopes, *condition.any_scopes))
|
||||
)
|
||||
if not unscoped_alternatives:
|
||||
return ()
|
||||
alternatives = ", ".join(str(index) for index in unscoped_alternatives)
|
||||
return (
|
||||
"every user workflow condition alternative must declare required_scopes or any_scopes; "
|
||||
f"unscoped alternative(s): {alternatives}",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DocumentationContext:
|
||||
registry: object
|
||||
|
||||
@@ -21,6 +21,7 @@ from govoplan_core.core.modules import (
|
||||
SUPPORTED_FRONTEND_ASSET_MANIFEST_CONTRACT_VERSION,
|
||||
SUPPORTED_MANIFEST_CONTRACT_VERSION,
|
||||
TenantSummaryProvider,
|
||||
user_workflow_scope_condition_issues,
|
||||
)
|
||||
from govoplan_core.core.versioning import format_version_range, version_range_is_valid, version_satisfies_range
|
||||
|
||||
@@ -369,6 +370,11 @@ def _validate_manifest_shape(manifest: ModuleManifest) -> None:
|
||||
_validate_manifest_frontend(manifest)
|
||||
for item in manifest.nav_items:
|
||||
_validate_nav_item(manifest.id, item)
|
||||
for topic in manifest.documentation:
|
||||
for issue in user_workflow_scope_condition_issues(topic):
|
||||
raise RegistryError(
|
||||
f"Module {manifest.id!r} documentation topic {topic.id!r}: {issue}"
|
||||
)
|
||||
|
||||
|
||||
def _validate_manifest_identity(manifest: ModuleManifest) -> None:
|
||||
|
||||
Reference in New Issue
Block a user