Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d36ab2747 | |||
| 94c000f351 |
@@ -26,7 +26,7 @@ It should not start from a full product manual. The current tenant, installed
|
|||||||
modules, enabled routes, permissions, and configuration decide what is shown as
|
modules, enabled routes, permissions, and configuration decide what is shown as
|
||||||
the default path.
|
the default path.
|
||||||
|
|
||||||
Tracking issue: `add-ideas/govoplan-docs#15`.
|
Tracking issue: `GovOPlaN/govoplan-docs#15`.
|
||||||
|
|
||||||
## Editorial Pillars
|
## Editorial Pillars
|
||||||
|
|
||||||
@@ -255,6 +255,9 @@ Workflow topics:
|
|||||||
- keep each step actionable
|
- keep each step actionable
|
||||||
- mention blockers where the user would encounter them
|
- mention blockers where the user would encounter them
|
||||||
- link to field/reference topics instead of repeating field tables
|
- link to field/reference topics instead of repeating field tables
|
||||||
|
- for every user-facing workflow, declare one or more conditions and put
|
||||||
|
`required_scopes` or `any_scopes` on every condition alternative; the release
|
||||||
|
gate rejects an unscoped alternative
|
||||||
|
|
||||||
Reference topics:
|
Reference topics:
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ opens the user projection: each contributed workflow still needs its own
|
|||||||
module, capability, permission, and runtime-policy conditions so baseline Help
|
module, capability, permission, and runtime-policy conditions so baseline Help
|
||||||
Center access does not imply authority to perform every documented task.
|
Center access does not imply authority to perform every documented task.
|
||||||
|
|
||||||
|
This permission binding is a strict source contract. Every user workflow topic
|
||||||
|
must declare at least one `DocumentationCondition`, and every alternative in
|
||||||
|
its `conditions` tuple must include `required_scopes` or `any_scopes`. Module,
|
||||||
|
capability, or configuration conditions alone are not sufficient because topic
|
||||||
|
conditions are alternatives: one unscoped alternative would bypass all scoped
|
||||||
|
ones. Manifest validation blocks a release containing such a topic, and Docs
|
||||||
|
omits a non-compliant runtime-provider topic from the user projection.
|
||||||
|
|
||||||
## Conditions
|
## Conditions
|
||||||
|
|
||||||
Documentation topics can declare:
|
Documentation topics can declare:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from govoplan_core.core.modules import (
|
|||||||
ModuleManifest,
|
ModuleManifest,
|
||||||
NavItem,
|
NavItem,
|
||||||
PermissionDefinition,
|
PermissionDefinition,
|
||||||
|
user_workflow_scope_condition_issues,
|
||||||
)
|
)
|
||||||
from govoplan_core.core.registry import PlatformRegistry
|
from govoplan_core.core.registry import PlatformRegistry
|
||||||
from govoplan_core.db.session import get_database
|
from govoplan_core.db.session import get_database
|
||||||
@@ -362,6 +363,7 @@ def _collect_documentation_topics(
|
|||||||
topics: list[tuple[str, DocumentationTopic]] = []
|
topics: list[tuple[str, DocumentationTopic]] = []
|
||||||
for manifest in registry.manifests():
|
for manifest in registry.manifests():
|
||||||
for topic in manifest.documentation:
|
for topic in manifest.documentation:
|
||||||
|
if not user_workflow_scope_condition_issues(topic):
|
||||||
topics.append((manifest.id, topic))
|
topics.append((manifest.id, topic))
|
||||||
if not manifest.documentation_providers:
|
if not manifest.documentation_providers:
|
||||||
continue
|
continue
|
||||||
@@ -393,6 +395,7 @@ def _collect_documentation_topics(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
for topic in provided_topics:
|
for topic in provided_topics:
|
||||||
|
if not user_workflow_scope_condition_issues(topic):
|
||||||
topics.append((manifest.id, topic))
|
topics.append((manifest.id, topic))
|
||||||
return topics
|
return topics
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,55 @@ class DocsContextTests(unittest.TestCase):
|
|||||||
self.assertNotIn("access.workflow.grant-user-access", workflow_ids)
|
self.assertNotIn("access.workflow.grant-user-access", workflow_ids)
|
||||||
self.assertNotIn("access.workflow.grant-user-access", {topic["id"] for topic in layers["available"]})
|
self.assertNotIn("access.workflow.grant-user-access", {topic["id"] for topic in layers["available"]})
|
||||||
|
|
||||||
|
def test_user_projection_fails_closed_for_unscoped_workflow_topics(self) -> None:
|
||||||
|
unscoped_static_topic = DocumentationTopic(
|
||||||
|
id="example.workflow.unscoped-static",
|
||||||
|
title="Unscoped static task",
|
||||||
|
summary="This topic must not be rendered.",
|
||||||
|
documentation_types=("user",),
|
||||||
|
metadata={"kind": "workflow"},
|
||||||
|
)
|
||||||
|
unscoped_runtime_topic = DocumentationTopic(
|
||||||
|
id="example.workflow.unscoped-runtime",
|
||||||
|
title="Unscoped runtime task",
|
||||||
|
summary="This provider topic must not be rendered.",
|
||||||
|
documentation_types=("user",),
|
||||||
|
conditions=(DocumentationCondition(required_modules=("example",)),),
|
||||||
|
metadata={"kind": "workflow"},
|
||||||
|
)
|
||||||
|
scoped_runtime_topic = DocumentationTopic(
|
||||||
|
id="example.workflow.scoped-runtime",
|
||||||
|
title="Scoped runtime task",
|
||||||
|
summary="This provider topic remains visible.",
|
||||||
|
documentation_types=("user",),
|
||||||
|
conditions=(DocumentationCondition(required_scopes=("docs:documentation:read",)),),
|
||||||
|
metadata={"kind": "workflow"},
|
||||||
|
)
|
||||||
|
registry = PlatformRegistry()
|
||||||
|
registry.register(ModuleManifest(
|
||||||
|
id="example",
|
||||||
|
name="Example",
|
||||||
|
version="9.9.9",
|
||||||
|
documentation=(unscoped_static_topic,),
|
||||||
|
documentation_providers=(
|
||||||
|
lambda context: (unscoped_runtime_topic, scoped_runtime_topic),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
layers = _classify_documentation(
|
||||||
|
registry,
|
||||||
|
FakePrincipal({"docs:documentation:read"}),
|
||||||
|
settings=None,
|
||||||
|
session=None,
|
||||||
|
documentation_type="user",
|
||||||
|
locale="en",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[topic["id"] for topic in _documentation_topic_groups(layers)["workflow"]],
|
||||||
|
["example.workflow.scoped-runtime"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_user_projection_is_a_bounded_safe_whitelist(self) -> None:
|
def test_user_projection_is_a_bounded_safe_whitelist(self) -> None:
|
||||||
registry = PlatformRegistry()
|
registry = PlatformRegistry()
|
||||||
registry.register(ModuleManifest(
|
registry.register(ModuleManifest(
|
||||||
|
|||||||
Reference in New Issue
Block a user