feat(workloads): define authoritative reference contract

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
tegwick 2026-08-22 14:03:07 +02:00
parent fa1f272ea4
commit b36b68bc57
8 changed files with 705 additions and 0 deletions

View file

@ -365,6 +365,23 @@ def main(argv: list[str] | None = None) -> int:
p_cache_export.add_argument("--output", required=True)
p_cache_export.add_argument("--force", action="store_true")
p_workload = sub.add_parser(
"workload",
help="Index or resolve authoritative rapp workload declarations",
)
workload_sub = p_workload.add_subparsers(dest="workload_command")
p_workload_index = workload_sub.add_parser(
"index", help="Index workload identities from rapp declarations"
)
p_workload_index.add_argument("--root", default=".", help="Fleet root or one rapp repo")
p_workload_resolve = workload_sub.add_parser(
"resolve", help="Resolve one explicit workload reference without inference"
)
p_workload_resolve.add_argument("--root", default=".", help="Fleet root or one rapp repo")
p_workload_resolve.add_argument("--rapp-id", required=True)
p_workload_resolve.add_argument("--name", required=True)
p_workload_resolve.add_argument("--deployable", default=None)
args = parser.parse_args(argv)
if args.version or args.command in (None, "version"):
@ -812,6 +829,24 @@ def main(argv: list[str] | None = None) -> int:
print(json.dumps(result, indent=2))
return 0 if result.get("ok") else 1
if args.command == "workload":
if not args.workload_command:
p_workload.print_help()
return 2
from repo_manager.workloads import index_workloads, resolve_workload
if args.workload_command == "index":
result = index_workloads(Path(args.root))
else:
result = resolve_workload(
Path(args.root),
rapp_id=args.rapp_id,
name=args.name,
deployable=args.deployable,
)
print(json.dumps(result, indent=2))
return 0 if result.get("ok") else 1
parser.print_help()
return 0

View file

@ -0,0 +1,238 @@
"""Read-only index and resolver for authoritative rapp workload declarations."""
from __future__ import annotations
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any
import yaml
REFERENCE_CONTRACT = "helixforge.workload-reference/v1"
@dataclass(frozen=True)
class WorkloadRecord:
rapp_id: str
name: str
declaration_repo: str
declaration_path: str
ownership_repo: str | None
readiness_state: str | None
data_classification: str | None
criticality: str | None
deployables: tuple[str, ...]
def to_dict(self) -> dict[str, Any]:
data = asdict(self)
data["deployables"] = list(self.deployables)
data["reference"] = {"rapp_id": self.rapp_id, "name": self.name}
return data
def _error(code: str, path: Path, message: str) -> dict[str, str]:
return {"code": code, "path": str(path), "message": message}
def _package_dirs(root: Path) -> list[Path]:
root = root.expanduser().resolve()
if root.name.startswith("rapp-"):
return [root]
return sorted(path for path in root.glob("rapp-*") if path.is_dir())
def _text(value: Any) -> str | None:
if not isinstance(value, str):
return None
stripped = value.strip()
return stripped or None
def index_workloads(root: Path) -> dict[str, Any]:
"""Index authoritative declarations without inventing missing identity."""
root = root.expanduser().resolve()
records: list[WorkloadRecord] = []
errors: list[dict[str, str]] = []
for package_dir in _package_dirs(root):
declaration = package_dir / "declarations" / "rapp.yaml"
if not declaration.is_file():
errors.append(
_error(
"declaration_missing",
declaration,
"rapp repository has no authoritative declarations/rapp.yaml",
)
)
continue
try:
raw = yaml.safe_load(declaration.read_text(encoding="utf-8")) or {}
except (OSError, yaml.YAMLError) as exc:
errors.append(_error("declaration_invalid", declaration, str(exc)))
continue
if not isinstance(raw, dict):
errors.append(
_error("declaration_invalid", declaration, "declaration root must be a mapping")
)
continue
if raw.get("kind") != "managed-workload-package":
errors.append(
_error(
"declaration_kind_invalid",
declaration,
"kind must be managed-workload-package",
)
)
continue
rapp_id = _text(raw.get("rapp_id"))
identity = raw.get("workload_identity")
name = _text(identity.get("name")) if isinstance(identity, dict) else None
if not rapp_id or not name:
errors.append(
_error(
"identity_missing",
declaration,
"rapp_id and workload_identity.name are required",
)
)
continue
if rapp_id != package_dir.name:
errors.append(
_error(
"rapp_repo_mismatch",
declaration,
f"rapp_id {rapp_id!r} does not match declaration repo {package_dir.name!r}",
)
)
continue
deployables: list[str] = []
composition = raw.get("composition")
members = composition.get("member_repos", []) if isinstance(composition, dict) else []
if not isinstance(members, list):
errors.append(
_error(
"composition_invalid",
declaration,
"composition.member_repos must be a list",
)
)
continue
for member in members:
if not isinstance(member, dict) or not isinstance(member.get("deployables"), list):
errors.append(
_error(
"composition_invalid",
declaration,
"every composition member must declare a deployables list",
)
)
continue
deployables.extend(
value for value in (_text(item) for item in member["deployables"]) if value
)
if not deployables:
errors.append(
_error(
"deployables_missing",
declaration,
"the workload declaration has no running deployables",
)
)
continue
records.append(
WorkloadRecord(
rapp_id=rapp_id,
name=name,
declaration_repo=package_dir.name,
declaration_path=str(declaration.relative_to(root)),
ownership_repo=_text(raw.get("ownership_repo")),
readiness_state=_text(raw.get("readiness_state")),
data_classification=_text(raw.get("data_classification")),
criticality=_text(raw.get("criticality")),
deployables=tuple(sorted(set(deployables))),
)
)
references: dict[tuple[str, str], list[WorkloadRecord]] = {}
deployable_owners: dict[str, list[WorkloadRecord]] = {}
for record in records:
references.setdefault((record.rapp_id, record.name), []).append(record)
for deployable in record.deployables:
deployable_owners.setdefault(deployable, []).append(record)
for reference, owners in sorted(references.items()):
if len(owners) > 1:
errors.append(
_error(
"reference_duplicate",
root,
f"workload reference {reference!r} resolves to {len(owners)} declarations",
)
)
for deployable, owners in sorted(deployable_owners.items()):
if len(owners) > 1:
errors.append(
_error(
"deployable_duplicate",
root,
f"deployable {deployable!r} belongs to more than one rapp: "
+ ", ".join(sorted(owner.rapp_id for owner in owners)),
)
)
return {
"ok": not errors,
"contract": REFERENCE_CONTRACT,
"root": str(root),
"declaration_count": len(records),
"workloads": [record.to_dict() for record in sorted(records, key=lambda r: r.rapp_id)],
"errors": errors,
}
def resolve_workload(
root: Path,
*,
rapp_id: str,
name: str,
deployable: str | None = None,
) -> dict[str, Any]:
"""Resolve an explicit reference, returning unknown instead of guessing."""
index = index_workloads(root)
matches = [
record
for record in index["workloads"]
if record["rapp_id"] == rapp_id and record["name"] == name
]
reference: dict[str, str] = {"rapp_id": rapp_id, "name": name}
if deployable:
reference["deployable"] = deployable
if len(matches) != 1:
return {
"ok": True,
"contract": REFERENCE_CONTRACT,
"status": "unknown",
"reference": reference,
"reason": "not_found" if not matches else "ambiguous",
"index_errors": index["errors"],
}
workload = matches[0]
if deployable and deployable not in workload["deployables"]:
return {
"ok": True,
"contract": REFERENCE_CONTRACT,
"status": "unknown",
"reference": reference,
"reason": "deployable_not_declared",
"index_errors": index["errors"],
}
return {
"ok": True,
"contract": REFERENCE_CONTRACT,
"status": "resolved",
"reference": reference,
"workload": workload,
"index_errors": index["errors"],
}