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:
parent
fa1f272ea4
commit
b36b68bc57
8 changed files with 705 additions and 0 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
| workplan | RMGR-WP-0007 | finished | — | workplans/RMGR-WP-0007-greenfield-rapp-wrap-efficiency.md |
|
| workplan | RMGR-WP-0007 | finished | — | workplans/RMGR-WP-0007-greenfield-rapp-wrap-efficiency.md |
|
||||||
| workplan | RMGR-WP-0008 | finished | — | workplans/RMGR-WP-0008-work-record-and-register-receiving-surface.md |
|
| workplan | RMGR-WP-0008 | finished | — | workplans/RMGR-WP-0008-work-record-and-register-receiving-surface.md |
|
||||||
| workplan | RMGR-WP-0009 | finished | — | workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md |
|
| workplan | RMGR-WP-0009 | finished | — | workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md |
|
||||||
|
| workplan | RMGR-WP-0010 | active | — | workplans/RMGR-WP-0010-authoritative-workload-references.md |
|
||||||
| task | RMGR-WP-0001-T01 | done | — | workplans/RMGR-WP-0001-foundation.md |
|
| task | RMGR-WP-0001-T01 | done | — | workplans/RMGR-WP-0001-foundation.md |
|
||||||
| task | RMGR-WP-0001-T02 | done | — | workplans/RMGR-WP-0001-foundation.md |
|
| task | RMGR-WP-0001-T02 | done | — | workplans/RMGR-WP-0001-foundation.md |
|
||||||
| task | RMGR-WP-0001-T03 | done | — | workplans/RMGR-WP-0001-foundation.md |
|
| task | RMGR-WP-0001-T03 | done | — | workplans/RMGR-WP-0001-foundation.md |
|
||||||
|
|
@ -79,5 +80,12 @@
|
||||||
| task | RMGR-WP-0009-T05 | done | — | workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md |
|
| task | RMGR-WP-0009-T05 | done | — | workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md |
|
||||||
| task | RMGR-WP-0009-T06 | done | — | workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md |
|
| task | RMGR-WP-0009-T06 | done | — | workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md |
|
||||||
| task | RMGR-WP-0009-T07 | done | — | workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md |
|
| task | RMGR-WP-0009-T07 | done | — | workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md |
|
||||||
|
| task | RMGR-WP-0010-T01 | done | — | workplans/RMGR-WP-0010-authoritative-workload-references.md |
|
||||||
|
| task | RMGR-WP-0010-T02 | done | — | workplans/RMGR-WP-0010-authoritative-workload-references.md |
|
||||||
|
| task | RMGR-WP-0010-T03 | done | — | workplans/RMGR-WP-0010-authoritative-workload-references.md |
|
||||||
|
| task | RMGR-WP-0010-T04 | todo | — | workplans/RMGR-WP-0010-authoritative-workload-references.md |
|
||||||
|
| task | RMGR-WP-0010-T05 | todo | — | workplans/RMGR-WP-0010-authoritative-workload-references.md |
|
||||||
|
| task | RMGR-WP-0010-T06 | wait | — | workplans/RMGR-WP-0010-authoritative-workload-references.md |
|
||||||
|
| task | RMGR-WP-0010-T07 | wait | — | workplans/RMGR-WP-0010-authoritative-workload-references.md |
|
||||||
| intake | RMGR-IN-0001 | open | — | intakes/intakes.md |
|
| intake | RMGR-IN-0001 | open | — | intakes/intakes.md |
|
||||||
| intake | RMGR-IN-0002 | open | — | intakes/intakes.md |
|
| intake | RMGR-IN-0002 | open | — | intakes/intakes.md |
|
||||||
|
|
|
||||||
92
docs/adr-004-authoritative-workload-declarations.md
Normal file
92
docs/adr-004-authoritative-workload-declarations.md
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
---
|
||||||
|
id: RMGR-ADR-004
|
||||||
|
type: architecture-decision-record
|
||||||
|
title: "Authoritative workload declarations and explicit consumer references"
|
||||||
|
status: accepted
|
||||||
|
decided: "2026-08-22"
|
||||||
|
deciders: ["Bernd Worsch", "repo-manager"]
|
||||||
|
related:
|
||||||
|
- RMGR-WP-0010
|
||||||
|
- RMASTER-ADR-0007
|
||||||
|
- ZONE-WP-0001-T03
|
||||||
|
- docs/workload-reference-contract_v1.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADR-004: Authoritative workload declarations and explicit consumer references
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
**Accepted** (2026-08-22).
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Zone Engine needs an authoritative workload subject before it can resolve
|
||||||
|
security-zone membership. Its estate measurement found nine rapp declarations,
|
||||||
|
but only one of 27 ops-warden credential lanes joined to one. Inferring a
|
||||||
|
workload from `path_template`, `owner_repo`, or a repository slug would silently
|
||||||
|
turn missing declarations into invented identity.
|
||||||
|
|
||||||
|
Railiance Master ADR-0007 already defines the family authority: every running
|
||||||
|
deployable belongs to exactly one rapp, while a rapp may contain multiple
|
||||||
|
deployables that share rollout and rollback fate. Its JSON schema is normative.
|
||||||
|
Repo Manager implements repository integration and must not fork that semantic
|
||||||
|
vocabulary.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
1. `rapp-*/declarations/rapp.yaml` is the authoritative declaration surface for
|
||||||
|
every managed running deployable, including operational and tooling
|
||||||
|
workloads that participate in controls.
|
||||||
|
2. There is no parallel declaration surface for managed workloads outside the
|
||||||
|
rapp family. A running unit awaiting rapp extraction is migration debt and
|
||||||
|
resolves as `unknown` to workload-based controls.
|
||||||
|
3. Not every operational subject is a workload. Human access, credential
|
||||||
|
patterns, broker actions, one-shot jobs, and infrastructure resources retain
|
||||||
|
their native actor, lane, activity, or resource identity. They must not be
|
||||||
|
represented as fake workloads merely to make a join total.
|
||||||
|
4. Authority is divided deliberately:
|
||||||
|
- Railiance Master owns the normative rapp/workload vocabulary and schema.
|
||||||
|
- Each rapp/package owner authors its declaration.
|
||||||
|
- Repo Manager owns discovery, validation integration, indexing, stable
|
||||||
|
reference resolution, and coverage findings.
|
||||||
|
- hub-core distributes normalized projections to cross-repository consumers.
|
||||||
|
- Consuming catalogs own their explicit pointers and never copy declaration
|
||||||
|
metadata as a second source.
|
||||||
|
5. A v1 workload reference is the pair `rapp_id` and
|
||||||
|
`workload_identity.name`. A consumer may additionally identify one
|
||||||
|
`composition.member_repos[].deployables[]` member. Repository names and path
|
||||||
|
segments are not reference fallbacks.
|
||||||
|
6. Resolution has three semantically distinct outcomes:
|
||||||
|
- `resolved`: exactly one authoritative declaration matches;
|
||||||
|
- `unknown`: the subject should be a workload but no unique declaration
|
||||||
|
resolves;
|
||||||
|
- `not-applicable`: the consumer explicitly declares that its subject is not
|
||||||
|
a workload.
|
||||||
|
Omission must not collapse these states.
|
||||||
|
7. Ops-warden may add an explicit workload reference to each applicable lane.
|
||||||
|
It owns that catalog field and its applicability marker; Repo Manager owns
|
||||||
|
resolution of the reference. Zone Engine consumes the resolved declaration
|
||||||
|
and the catalog pointer and performs no name or path inference.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- Operational and tooling runtimes need rapp coverage just like application
|
||||||
|
workloads. Their ownership repositories do not become package repositories.
|
||||||
|
- Existing pre-rapp runtimes may continue operating during migration, but
|
||||||
|
workload-based controls must expose their identity as unknown.
|
||||||
|
- Repo Manager can detect missing declarations in `rapp-*` repositories,
|
||||||
|
duplicate reference pairs, and deployables claimed by more than one rapp.
|
||||||
|
- Full estate coverage still requires a runtime/deployable observation source;
|
||||||
|
repository names alone cannot prove that every running unit is declared.
|
||||||
|
- Consumer catalogs remain pointer layers. A resolved reference gives access to
|
||||||
|
current classification, criticality, readiness, and placement without copying
|
||||||
|
those values.
|
||||||
|
|
||||||
|
## Rejected alternatives
|
||||||
|
|
||||||
|
- **A generic second `workloads.yaml`.** This would create two authorities for
|
||||||
|
the same managed deployable and contradict RMASTER-ADR-0007.
|
||||||
|
- **Path or repository-name inference.** Current `qonto`, `rapp-qonto`, and
|
||||||
|
`qonto-assistant` drift proves that convention is not identity.
|
||||||
|
- **Making every credential lane a workload.** Several lanes describe actors,
|
||||||
|
patterns, or control-plane operations rather than running deployables.
|
||||||
75
docs/workload-reference-contract_v1.md
Normal file
75
docs/workload-reference-contract_v1.md
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
---
|
||||||
|
id: RMGR-CONTRACT-WORKLOAD-REFERENCE-0001
|
||||||
|
type: contract
|
||||||
|
title: "Authoritative workload reference and resolution contract"
|
||||||
|
version: "1"
|
||||||
|
status: active
|
||||||
|
created: "2026-08-22"
|
||||||
|
updated: "2026-08-22"
|
||||||
|
workplan_task: RMGR-WP-0010-T02
|
||||||
|
---
|
||||||
|
|
||||||
|
# Authoritative workload reference and resolution contract v1
|
||||||
|
|
||||||
|
## Authority
|
||||||
|
|
||||||
|
The referenced record is authoritative at
|
||||||
|
`rapp-*/declarations/rapp.yaml`, under the normative Railiance Master rapp
|
||||||
|
schema. Repo Manager indexes and resolves that file; it does not redefine or
|
||||||
|
copy its semantic fields.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
workload_ref:
|
||||||
|
rapp_id: rapp-issue-core
|
||||||
|
name: issue-core
|
||||||
|
deployable: issue-core # optional
|
||||||
|
```
|
||||||
|
|
||||||
|
`rapp_id` and `name` are required and must exactly equal `rapp_id` and
|
||||||
|
`workload_identity.name` in one declaration. `deployable`, when present, must
|
||||||
|
exactly equal a member of `composition.member_repos[].deployables[]` in that
|
||||||
|
same declaration.
|
||||||
|
|
||||||
|
Names are case-sensitive canonical slugs. A resolver must not strip prefixes,
|
||||||
|
parse secret paths, consult `owner_repo`, or substitute a repository name.
|
||||||
|
Renaming either required field is a reference migration, not an alias inferred
|
||||||
|
by the reader.
|
||||||
|
|
||||||
|
## Resolution outcomes
|
||||||
|
|
||||||
|
| Outcome | Meaning | Consumer behavior |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `resolved` | Exactly one declaration matches, including the optional deployable. | Consume the authoritative declaration projection. |
|
||||||
|
| `unknown` | A workload reference is expected but missing, ambiguous, invalid, or unresolved. | Fail closed where policy requires identity; expose the reason. |
|
||||||
|
| `not-applicable` | The catalog owner asserts that the subject is not a workload. | Use the subject's native typed identity; do not call it unknown. |
|
||||||
|
|
||||||
|
Repo Manager produces `resolved` or `unknown`. `not-applicable` is an explicit
|
||||||
|
consumer-catalog assertion because only that catalog knows what its lane or
|
||||||
|
control represents. Consumers must represent the distinction rather than using
|
||||||
|
an absent field for both states.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rmgr workload index --root /home/worsch
|
||||||
|
rmgr workload resolve \
|
||||||
|
--root /home/worsch \
|
||||||
|
--rapp-id rapp-issue-core \
|
||||||
|
--name issue-core \
|
||||||
|
--deployable issue-core
|
||||||
|
```
|
||||||
|
|
||||||
|
The index reports missing rapp declarations, duplicate reference pairs, and
|
||||||
|
deployables claimed by more than one rapp. Resolution is read-only and returns
|
||||||
|
`unknown` instead of applying a naming heuristic.
|
||||||
|
|
||||||
|
## Consumer obligations
|
||||||
|
|
||||||
|
- Ops-warden owns its lane field and whether a lane is workload-applicable.
|
||||||
|
- Zone Engine consumes explicit references plus declaration projections.
|
||||||
|
- hub-core may transport the normalized records but does not become their
|
||||||
|
authority.
|
||||||
|
- Copied classification, criticality, readiness, or placement values are
|
||||||
|
caches and must retain source identity and observation metadata.
|
||||||
35
docs/workload-reference-contract_v1.yaml
Normal file
35
docs/workload-reference-contract_v1.yaml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
contract: helixforge.workload-reference
|
||||||
|
version: 1
|
||||||
|
status: active
|
||||||
|
authority:
|
||||||
|
semantic_schema: railiance-master/schemas/rapp.schema.json
|
||||||
|
record_pattern: rapp-*/declarations/rapp.yaml
|
||||||
|
indexer: repo-manager
|
||||||
|
reference:
|
||||||
|
required:
|
||||||
|
- rapp_id
|
||||||
|
- name
|
||||||
|
optional:
|
||||||
|
- deployable
|
||||||
|
source_fields:
|
||||||
|
rapp_id: rapp_id
|
||||||
|
name: workload_identity.name
|
||||||
|
deployable: composition.member_repos[].deployables[]
|
||||||
|
forbidden_fallbacks:
|
||||||
|
- repository_name
|
||||||
|
- owner_repo
|
||||||
|
- path_segment
|
||||||
|
resolution:
|
||||||
|
producer_states:
|
||||||
|
- resolved
|
||||||
|
- unknown
|
||||||
|
consumer_asserted_states:
|
||||||
|
- not-applicable
|
||||||
|
unknown_reasons:
|
||||||
|
- not_found
|
||||||
|
- ambiguous
|
||||||
|
- deployable_not_declared
|
||||||
|
consumer_rules:
|
||||||
|
ops-warden: owns lane reference and applicability
|
||||||
|
zone-engine: consumes references without identity inference
|
||||||
|
hub-core: transports normalized projection without assuming authority
|
||||||
|
|
@ -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("--output", required=True)
|
||||||
p_cache_export.add_argument("--force", action="store_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)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
if args.version or args.command in (None, "version"):
|
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))
|
print(json.dumps(result, indent=2))
|
||||||
return 0 if result.get("ok") else 1
|
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()
|
parser.print_help()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
|
||||||
238
src/repo_manager/workloads.py
Normal file
238
src/repo_manager/workloads.py
Normal 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"],
|
||||||
|
}
|
||||||
104
tests/test_workloads.py
Normal file
104
tests/test_workloads.py
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from repo_manager.workloads import index_workloads, resolve_workload
|
||||||
|
|
||||||
|
|
||||||
|
def _declaration(
|
||||||
|
root: Path,
|
||||||
|
rapp_id: str,
|
||||||
|
name: str,
|
||||||
|
deployables: list[str],
|
||||||
|
) -> None:
|
||||||
|
path = root / rapp_id / "declarations" / "rapp.yaml"
|
||||||
|
path.parent.mkdir(parents=True)
|
||||||
|
rendered = "\n".join(f" - {item}" for item in deployables)
|
||||||
|
path.write_text(
|
||||||
|
f"""kind: managed-workload-package
|
||||||
|
rapp_id: {rapp_id}
|
||||||
|
ownership_repo: {name}
|
||||||
|
readiness_state: declared
|
||||||
|
workload_identity:
|
||||||
|
name: {name}
|
||||||
|
data_classification: confidential
|
||||||
|
criticality: high
|
||||||
|
composition:
|
||||||
|
member_repos:
|
||||||
|
- repo: {rapp_id}
|
||||||
|
deployables:
|
||||||
|
{rendered}
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_indexes_authoritative_workload_identity_and_deployables(tmp_path: Path) -> None:
|
||||||
|
_declaration(tmp_path, "rapp-issue-core", "issue-core", ["issue-core"])
|
||||||
|
|
||||||
|
result = index_workloads(tmp_path)
|
||||||
|
|
||||||
|
assert result["ok"] is True
|
||||||
|
assert result["declaration_count"] == 1
|
||||||
|
assert result["workloads"][0]["reference"] == {
|
||||||
|
"rapp_id": "rapp-issue-core",
|
||||||
|
"name": "issue-core",
|
||||||
|
}
|
||||||
|
assert result["workloads"][0]["deployables"] == ["issue-core"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_reports_missing_rapp_declaration_without_inventing_identity(tmp_path: Path) -> None:
|
||||||
|
(tmp_path / "rapp-undeclared").mkdir()
|
||||||
|
|
||||||
|
result = index_workloads(tmp_path)
|
||||||
|
|
||||||
|
assert result["ok"] is False
|
||||||
|
assert result["workloads"] == []
|
||||||
|
assert result["errors"][0]["code"] == "declaration_missing"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reports_deployable_owned_by_more_than_one_rapp(tmp_path: Path) -> None:
|
||||||
|
_declaration(tmp_path, "rapp-one", "one", ["shared-api"])
|
||||||
|
_declaration(tmp_path, "rapp-two", "two", ["shared-api"])
|
||||||
|
|
||||||
|
result = index_workloads(tmp_path)
|
||||||
|
|
||||||
|
assert result["ok"] is False
|
||||||
|
assert any(error["code"] == "deployable_duplicate" for error in result["errors"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolves_only_explicit_rapp_and_workload_pair(tmp_path: Path) -> None:
|
||||||
|
_declaration(tmp_path, "rapp-qonto", "qonto", ["qonto-assistant"])
|
||||||
|
|
||||||
|
resolved = resolve_workload(
|
||||||
|
tmp_path,
|
||||||
|
rapp_id="rapp-qonto",
|
||||||
|
name="qonto",
|
||||||
|
deployable="qonto-assistant",
|
||||||
|
)
|
||||||
|
unknown = resolve_workload(tmp_path, rapp_id="rapp-qonto", name="rapp-qonto")
|
||||||
|
|
||||||
|
assert resolved["status"] == "resolved"
|
||||||
|
assert resolved["workload"]["name"] == "qonto"
|
||||||
|
assert unknown == {
|
||||||
|
"ok": True,
|
||||||
|
"contract": "helixforge.workload-reference/v1",
|
||||||
|
"status": "unknown",
|
||||||
|
"reference": {"rapp_id": "rapp-qonto", "name": "rapp-qonto"},
|
||||||
|
"reason": "not_found",
|
||||||
|
"index_errors": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_deployable_is_not_inferred_from_repository_name(tmp_path: Path) -> None:
|
||||||
|
_declaration(tmp_path, "rapp-openbao", "openbao", ["openbao-api"])
|
||||||
|
|
||||||
|
result = resolve_workload(
|
||||||
|
tmp_path,
|
||||||
|
rapp_id="rapp-openbao",
|
||||||
|
name="openbao",
|
||||||
|
deployable="rapp-openbao",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["status"] == "unknown"
|
||||||
|
assert result["reason"] == "deployable_not_declared"
|
||||||
118
workplans/RMGR-WP-0010-authoritative-workload-references.md
Normal file
118
workplans/RMGR-WP-0010-authoritative-workload-references.md
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
---
|
||||||
|
id: RMGR-WP-0010
|
||||||
|
type: workplan
|
||||||
|
title: "Authoritative workload declarations and explicit references"
|
||||||
|
domain: infotech
|
||||||
|
repo: repo-manager
|
||||||
|
status: active
|
||||||
|
owner: codex
|
||||||
|
topic_slug: infotech
|
||||||
|
created: "2026-08-22"
|
||||||
|
updated: "2026-08-22"
|
||||||
|
related:
|
||||||
|
- ZONE-WP-0001
|
||||||
|
- RMASTER-ADR-0007
|
||||||
|
- WARDEN-WP-0032
|
||||||
|
---
|
||||||
|
|
||||||
|
# Authoritative workload declarations and explicit references
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make authoritative workload identities resolvable across repository boundaries
|
||||||
|
without inventing them from paths or repository names, while preserving
|
||||||
|
Railiance Master schema ownership and consumer-catalog boundaries.
|
||||||
|
|
||||||
|
## Record the authority ruling
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RMGR-WP-0010-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Record the authoritative declaration surface, owner split, non-workload
|
||||||
|
boundary, and resolved/unknown/not-applicable semantics.
|
||||||
|
|
||||||
|
**Result (2026-08-22):** accepted as `RMGR-ADR-004`. Railiance Master owns the
|
||||||
|
normative vocabulary; rapp repositories own their declarations; Repo Manager
|
||||||
|
owns integration and resolution; consumers own explicit references.
|
||||||
|
|
||||||
|
## Publish the workload-reference contract
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RMGR-WP-0010-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a stable reference that can be carried by pointer catalogs without
|
||||||
|
copying workload metadata or depending on a storage schema.
|
||||||
|
|
||||||
|
**Result (2026-08-22):** `docs/workload-reference-contract_v1.{md,yaml}` defines
|
||||||
|
the `(rapp_id, workload_identity.name)` key, optional deployable, forbidden
|
||||||
|
fallbacks, and three resolution outcomes.
|
||||||
|
|
||||||
|
## Implement read-only indexing and resolution
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RMGR-WP-0010-T03
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Index all checked-out rapp declarations, detect missing/duplicate authority,
|
||||||
|
and resolve exact references without inference.
|
||||||
|
|
||||||
|
**Result (2026-08-22):** `rmgr workload index|resolve` indexes the nine current
|
||||||
|
declarations, rejects duplicate deployable ownership, reports missing rapp
|
||||||
|
declarations, and returns `unknown` rather than guessing. Unit tests cover
|
||||||
|
positive resolution and fail-closed cases.
|
||||||
|
|
||||||
|
## Publish the normalized projection port
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RMGR-WP-0010-T04
|
||||||
|
status: todo
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
Expose the v1 index through the Repo Manager observation contract and hub-core
|
||||||
|
port with source revision, declaration path, and observation timestamp. Do not
|
||||||
|
expose private persistence models.
|
||||||
|
|
||||||
|
## Add authoritative estate coverage
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RMGR-WP-0010-T05
|
||||||
|
status: todo
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Join declarations to an authoritative runtime/deployable observation source and
|
||||||
|
report running units lacking rapp coverage. Repository names are insufficient
|
||||||
|
evidence. Separate non-workload operational subjects from undeclared workloads.
|
||||||
|
|
||||||
|
## Adopt explicit ops-warden references
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RMGR-WP-0010-T06
|
||||||
|
status: wait
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Ops-warden owns its catalog schema. Add an explicit workload reference and an
|
||||||
|
unambiguous not-applicable/unknown distinction, then validate every resolved
|
||||||
|
reference through Repo Manager. Do not copy classification or criticality.
|
||||||
|
|
||||||
|
## Prove Zone Engine consumption
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RMGR-WP-0010-T07
|
||||||
|
status: wait
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Zone Engine consumes the declaration projection and explicit catalog references,
|
||||||
|
reports unknown subjects without inference, and demonstrates that path/repo-name
|
||||||
|
drift cannot alter membership.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue