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

@ -17,6 +17,7 @@
| 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-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-T02 | 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-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-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-0002 | open | — | intakes/intakes.md |

View 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.

View 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.

View 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

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"],
}

104
tests/test_workloads.py Normal file
View 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"

View 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.