feat(orchestration): compose security scenarios
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
This commit is contained in:
tegwick 2026-08-23 12:40:52 +02:00
parent ad46cc89fc
commit d96aab2321
20 changed files with 1464 additions and 30 deletions

View file

@ -60,6 +60,9 @@ TRUST_STATES = {
"tenant_onboarding_trust",
}
SCENARIO_AUTHORITIES = {"platform", "netkingdom", "tenant"}
SECRET_REFERENCE_PATTERN = re.compile(
r"(?:[a-z][a-z0-9+.-]*://\S+|(?:/|\./|\.\./)\S+)"
)
@dataclass
@ -127,6 +130,10 @@ def is_type(value: Any, declared_type: str) -> bool:
return False
def is_secret_reference(value: Any) -> bool:
return isinstance(value, str) and SECRET_REFERENCE_PATTERN.fullmatch(value) is not None
def validate_constraints(value: Any, param: dict[str, Any], path: str) -> list[Issue]:
issues: list[Issue] = []
constraints = param.get("constraints", {})
@ -277,6 +284,18 @@ def validate_parameters(spec: dict[str, Any]) -> list[Issue]:
if item.get("sensitivity") in {"security_sensitive", "secret_reference"} and item.get("tuning_authority") == "tenant_tunable":
issues.append(issue("ERROR", path, "security-sensitive parameters cannot be tenant_tunable"))
if item.get("sensitivity") == "secret_reference":
if declared_type != "string":
issues.append(issue("ERROR", f"{path}.type", "secret_reference parameters must use string type"))
if "default" in item and not is_secret_reference(item["default"]):
issues.append(
issue(
"ERROR",
f"{path}.default",
"secret_reference default must be a URI or explicit absolute/relative path",
)
)
if "default" in item and declared_type in PARAM_TYPES and not is_type(item["default"], declared_type):
issues.append(issue("ERROR", f"{path}.default", f"default does not match type {declared_type!r}"))
if declared_type in PARAM_TYPES:
@ -405,6 +424,15 @@ def validate_override_allowed(param: dict[str, Any], value: Any, scenario_author
if scenario_authority == "tenant" and sensitivity in {"security_sensitive", "secret_reference"}:
issues.append(issue("ERROR", path, f"tenant authority cannot override {sensitivity} parameter {name!r}"))
if sensitivity == "secret_reference" and not is_secret_reference(value):
issues.append(
issue(
"ERROR",
path,
f"override for {name!r} must be a secret-reference URI or explicit absolute/relative path",
)
)
if not is_type(value, declared_type):
issues.append(issue("ERROR", path, f"override for {name!r} does not match type {declared_type!r}"))
issues.extend(validate_constraints(value, param, path))
@ -433,10 +461,23 @@ def compose_scenario(declarations: list[Declaration], scenario: dict[str, Any])
selected: list[Declaration] = []
for cap_id in required_caps:
matches = [declaration for declaration in declarations if cap_id in declaration.capabilities]
matches = sorted(
[declaration for declaration in declarations if cap_id in declaration.capabilities],
key=lambda declaration: declaration.id,
)
if not matches:
issues.append(issue("ERROR", "scenario.requires.capabilities", f"no declaration provides {cap_id!r}"))
continue
if len(matches) > 1:
issues.append(
issue(
"ERROR",
"scenario.requires.capabilities",
f"ambiguous providers {[declaration.id for declaration in matches]}; "
"use the Security Scenario Composer to pin one explicitly",
)
)
continue
selected.append(matches[0])
# Preserve order while deduplicating declarations selected for several capabilities.
@ -444,6 +485,16 @@ def compose_scenario(declarations: list[Declaration], scenario: dict[str, Any])
for declaration in selected:
selected_by_id.setdefault(declaration.id, declaration)
for declaration_id in overrides:
if declaration_id not in selected_by_id:
issues.append(
issue(
"ERROR",
f"scenario.parameter_overrides.{declaration_id}",
"override targets an unselected declaration",
)
)
composed = {
"scenario": scenario.get("id", "scenario:unnamed"),
"authority": authority,