feat(policy)!: finalize public policy protobuf contract

BREAKING CHANGE: Policy YAML and public policy APIs now use the protobuf-shaped openshell.policy.v1.PolicyDocument contract.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
This commit is contained in:
Piotr Mlocek
2026-09-19 00:19:16 -07:00
parent f9c76ac429
commit aca0575a50
182 changed files with 8895 additions and 1382 deletions
+3
View File
@@ -7,6 +7,7 @@ from __future__ import annotations
from .errors import ErrorInfo, FieldViolation, GatewayError, from_grpc_error
from .mutations import DeletionOutcome, DeletionResult
from .policy import PolicyDocument, validate_policy_document
from .sandbox import (
ClientCredentialsAuth,
ExecChunk,
@@ -44,6 +45,7 @@ __all__ = [
"GatewayError",
"Page",
"Pager",
"PolicyDocument",
"Sandbox",
"SandboxClient",
"SandboxError",
@@ -57,4 +59,5 @@ __all__ = [
"WorkspaceRef",
"__version__",
"from_grpc_error",
"validate_policy_document",
]
+18
View File
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Validation helpers for the public authored policy contract."""
from __future__ import annotations
import protovalidate
from ._proto import policy_pb2
PolicyDocument = policy_pb2.PolicyDocument
def validate_policy_document(document: PolicyDocument) -> PolicyDocument:
"""Validate a policy against the portable rules declared in policy.proto."""
protovalidate.validate(document)
return document
+54
View File
@@ -0,0 +1,54 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import protovalidate
import pytest
from openshell.policy import PolicyDocument, validate_policy_document
def test_policy_document_validation_runs_portable_rules() -> None:
assert validate_policy_document(PolicyDocument(version=1)).version == 1
def test_policy_document_validation_reports_portable_rule_ids() -> None:
documents: list[tuple[str, PolicyDocument, str]] = []
documents.append(("version", PolicyDocument(), "uint32.const"))
missing_ports = PolicyDocument(version=1)
missing_ports.network_policies["api"].endpoints.add(host="api.example.com")
documents.append(("missing ports", missing_ports, "repeated.min_items"))
duplicate_ports = PolicyDocument(version=1)
duplicate_ports.network_policies["api"].endpoints.add(
host="api.example.com", ports=[443, 443]
)
documents.append(("duplicate ports", duplicate_ports, "repeated.unique"))
port_range = PolicyDocument(version=1)
port_range.network_policies["api"].endpoints.add(
host="api.example.com", ports=[65536]
)
documents.append(("port range", port_range, "uint32.gte_lte"))
binary_path = PolicyDocument(version=1)
binary_path.network_policies["api"].binaries.add(path="")
documents.append(("binary path", binary_path, "string.min_len"))
matcher_choice = PolicyDocument(version=1)
endpoint = matcher_choice.network_policies["api"].endpoints.add(
host="api.example.com", ports=[443]
)
endpoint.rules.add().allow.query["owner"].SetInParent()
documents.append(("matcher choice", matcher_choice, "required"))
for name, document, rule_id in documents:
with pytest.raises(protovalidate.ValidationError) as error:
validate_policy_document(document)
assert rule_id in {
violation.proto.rule_id for violation in error.value.violations
}, name
+5
View File
@@ -30,6 +30,7 @@ from ._proto import (
)
from .errors import _error_mapping_channel
from .mutations import DeletionOutcome, DeletionResult
from .policy import validate_policy_document
_ClientCallDetailsBase = namedtuple(
"_ClientCallDetailsBase",
@@ -766,6 +767,8 @@ class SandboxClient:
labels: Mapping[str, str] | None = None,
) -> SandboxRef:
request_spec = spec if spec is not None else _default_spec()
if request_spec.HasField("policy"):
validate_policy_document(request_spec.policy)
response = self._stub.CreateSandbox(
openshell_pb2.CreateSandboxRequest(
workspace_scope=_workspace_scope(workspace),
@@ -792,6 +795,8 @@ class SandboxClient:
if not workload_template.strip():
raise SandboxError("workload_template is required")
request_spec = spec if spec is not None else openshell_pb2.SandboxSpec()
if request_spec.HasField("policy"):
validate_policy_document(request_spec.policy)
response = self._stub.CreateSandbox(
openshell_pb2.CreateSandboxRequest(
workspace_scope=_workspace_scope(workspace),