From 890f3b05b596047a31605c544c56ed8a4f011868 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 22 Aug 2026 14:15:48 +0200 Subject: [PATCH] feat(handoffs): publish owner task interfaces Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d --- WORK-RECORDS.md | 10 +- docs/adr-005-owner-task-interfaces.md | 70 +++++++ docs/owner-task-interface-contract_v1.md | 92 +++++++++ .../ops-warden-workload-reference-v1.yaml | 49 +++++ interfaces/rapp-workload-coverage-v1.yaml | 50 +++++ .../workload-projection-transport-v1.yaml | 47 +++++ .../zone-engine-workload-resolution-v1.yaml | 49 +++++ schemas/owner-task-interface.schema.yaml | 72 +++++++ src/repo_manager/cli.py | 25 +++ src/repo_manager/owner_interfaces.py | 177 ++++++++++++++++++ tests/test_owner_interfaces.py | 74 ++++++++ ...-0010-authoritative-workload-references.md | 77 ++++---- 12 files changed, 754 insertions(+), 38 deletions(-) create mode 100644 docs/adr-005-owner-task-interfaces.md create mode 100644 docs/owner-task-interface-contract_v1.md create mode 100644 interfaces/ops-warden-workload-reference-v1.yaml create mode 100644 interfaces/rapp-workload-coverage-v1.yaml create mode 100644 interfaces/workload-projection-transport-v1.yaml create mode 100644 interfaces/zone-engine-workload-resolution-v1.yaml create mode 100644 schemas/owner-task-interface.schema.yaml create mode 100644 src/repo_manager/owner_interfaces.py create mode 100644 tests/test_owner_interfaces.py diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index da29adf..17bd88f 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -17,7 +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 | +| workplan | RMGR-WP-0010 | finished | — | 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 | @@ -83,9 +83,9 @@ | 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 | wait | — | workplans/RMGR-WP-0010-authoritative-workload-references.md | -| task | RMGR-WP-0010-T05 | wait | — | 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 | +| task | RMGR-WP-0010-T04 | done | — | workplans/RMGR-WP-0010-authoritative-workload-references.md | +| task | RMGR-WP-0010-T05 | done | — | workplans/RMGR-WP-0010-authoritative-workload-references.md | +| task | RMGR-WP-0010-T06 | done | — | workplans/RMGR-WP-0010-authoritative-workload-references.md | +| task | RMGR-WP-0010-T07 | done | — | workplans/RMGR-WP-0010-authoritative-workload-references.md | | intake | RMGR-IN-0001 | open | — | intakes/intakes.md | | intake | RMGR-IN-0002 | open | — | intakes/intakes.md | diff --git a/docs/adr-005-owner-task-interfaces.md b/docs/adr-005-owner-task-interfaces.md new file mode 100644 index 0000000..96a082b --- /dev/null +++ b/docs/adr-005-owner-task-interfaces.md @@ -0,0 +1,70 @@ +--- +id: RMGR-ADR-005 +type: architecture-decision-record +title: "Owner-task interfaces for cross-repository adoption" +status: accepted +decided: "2026-08-22" +deciders: ["Bernd Worsch", "repo-manager"] +related: + - RMGR-WP-0010 + - docs/owner-task-interface-contract_v1.md + - canon-work-record-types +--- + +# ADR-005: Owner-task interfaces for cross-repository adoption + +## Status + +**Accepted** (2026-08-22). + +## Context + +A producer can identify work that belongs in another repository before the +owner has created a task for it. Sending only a prose request makes the next +coding agent reconstruct the objective, inputs, constraints, and acceptance +criteria. Keeping a duplicate waiting task in the producer workplan then makes +two agents carry one todo. + +The fleet work-record registry is deliberately closed. An interface offer must +therefore not become a new task-like lifecycle or a second backlog. + +## Decision + +1. A producer may publish an `OwnerTaskInterface`: a versioned, immutable + proposal containing the complete owner-ready objective, authoritative + inputs, deliverables, constraints, acceptance criteria, and verification. +2. The interface is not a work record. It has no assignee or lifecycle status + and does not replace the target owner's workplan/task/decision. +3. The target owner records `approved`, `amended`, or `rejected` in its own + authoritative work record. Approval cites the interface id and exact Git + revision. An amendment records the delta rather than silently changing the + imported meaning. +4. Once the producer publishes and routes a complete interface, its handoff + task is done. Adoption and implementation are tracked only in the target + owner's work record; the producer does not retain a duplicate todo. +5. Dependencies that still block producer-owned implementation remain normal + producer tasks. The interface pattern is only for work whose implementation + authority has crossed the repository boundary. +6. Interfaces contain no credentials, secret values, mutable environment + state, or copied authority. They reference authoritative contracts and + evidence by stable path/id and revision. + +## Consequences + +- Owners can approve and use the supplied task specification directly. +- Later agents do not need to recover intent from messages or re-plan the work. +- Owner amendments stay explicit and auditable. +- Work queues contain one implementation todo, in the repository that can + actually complete it. +- Messages become routing notifications; they are not the only copy of the + implementation contract. + +## Rejected alternatives + +- **Duplicate tasks in producer and consumer workplans.** They drift and need + manual closure coordination. +- **Prose-only messages.** They are useful routing evidence but too weak to be + an executable interface. +- **A new `handoff-task` work-record kind.** The existing task, intake, and + decision kinds already own lifecycle; adding another would create a parallel + backlog ontology. diff --git a/docs/owner-task-interface-contract_v1.md b/docs/owner-task-interface-contract_v1.md new file mode 100644 index 0000000..e94a484 --- /dev/null +++ b/docs/owner-task-interface-contract_v1.md @@ -0,0 +1,92 @@ +--- +id: RMGR-CONTRACT-OWNER-TASK-INTERFACE-0001 +type: contract +title: "Owner-task interface contract" +version: "1" +status: active +created: "2026-08-22" +updated: "2026-08-22" +related: + - RMGR-ADR-005 + - RMGR-WP-0010 +--- + +# Owner-task interface contract v1 + +An owner-task interface is a complete proposal that another repository can +approve and adopt by reference. It transports implementation intent, not task +lifecycle. + +## Required shape + +```yaml +apiVersion: helixforge.repo-manager/v1 +kind: OwnerTaskInterface +metadata: + id: helixforge.example.consumer-port.v1 + title: Adopt the example consumer port +source: + repo: producing-repo + workplan_task: PRODUCER-WP-0001-T04 +target: + repo: consuming-repo + owner_agent: consuming-repo +approval: + dispositions: [approved, amended, rejected] + record_in: target owner work record +task: + title: Adopt the example consumer port + priority: medium + objective: Consume the producer contract without copying its authority. + inputs: + - ref: producing-repo/docs/example-contract_v1.md + authority: producing-repo + deliverables: + - Implement the consumer adapter. + constraints: + - Do not infer missing identity. + acceptance: + - A contract fixture passes through the consumer boundary. + verification: + - make test +``` + +`inputs`, `constraints`, and `verification` are optional. The objective, +deliverables, and acceptance criteria are mandatory and owner-ready. + +## Approval and adoption + +The target owner records one disposition in its existing workplan task or +decision: + +```yaml +interface_id: helixforge.example.consumer-port.v1 +source_revision: +disposition: approved # approved | amended | rejected +``` + +This may be prose or structured metadata according to the target repository's +existing convention. `amended` must cite the delta. The target task remains the +only lifecycle-bearing implementation record. + +The source task is complete after the validated interface is committed and its +owner is notified. A later producer task is warranted only if an approved +amendment requires producer-side changes. + +## Discovery + +```bash +rmgr owner-interface validate --path interfaces +rmgr owner-interface validate --path interfaces --owner ops-warden +``` + +The command validates all interface files before returning their content. An +owner filter matches either `target.repo` or `target.owner_agent`. + +## Authority and safety + +- The Git revision plus `metadata.id` pins the offered meaning. +- Referenced records remain authoritative in their source repositories. +- Interface files never contain secrets or mutable runtime observations. +- An interface must not contain `status`, State Hub ids, or an assignee: those + would turn it into an unregistered work-record lifecycle. diff --git a/interfaces/ops-warden-workload-reference-v1.yaml b/interfaces/ops-warden-workload-reference-v1.yaml new file mode 100644 index 0000000..22673f2 --- /dev/null +++ b/interfaces/ops-warden-workload-reference-v1.yaml @@ -0,0 +1,49 @@ +apiVersion: helixforge.repo-manager/v1 +kind: OwnerTaskInterface +metadata: + id: helixforge.workloads.ops-warden-reference.v1 + title: Add explicit workload references to ops-warden lanes +source: + repo: repo-manager + workplan_task: RMGR-WP-0010-T06 +target: + repo: ops-warden + owner_agent: ops-warden + suggested_work_record: WARDEN-WP-0032 +approval: + dispositions: [approved, amended, rejected] + record_in: WARDEN-WP-0032 task or an ops-warden owner decision +task: + title: Adopt explicit workload references in the lane catalog + priority: high + objective: >- + Let workload-applicable lanes point to authoritative rapp workload identity + while keeping the catalog a pointer layer. + inputs: + - ref: repo-manager/docs/workload-reference-contract_v1.md + authority: repo-manager + - ref: ops-warden/registry/routing/catalog.yaml + authority: ops-warden + deliverables: + - >- + Add workload_ref with required rapp_id and name and optional deployable to + every workload-applicable lane. + - >- + Add an explicit applicability representation that distinguishes + not-applicable from an unresolved or invalid workload reference. + - Validate every supplied reference through Repo Manager exact resolution. + - Preserve native typed identity for lanes that do not represent workloads. + constraints: + - Do not copy workload classification, criticality, readiness, or placement into the catalog. + - Do not infer workload identity from path_template, owner_repo, or repository names. + - An absent pointer must not ambiguously mean both unknown and not-applicable. + acceptance: + - Every workload-applicable lane resolves exactly or reports unknown with a reason. + - Every non-workload lane is explicitly not-applicable. + - Catalog validation fails on malformed, ambiguous, or undeclared references. + - Existing lane identity and routing authority remain owned by ops-warden. + verification: + - make test + - make lint + - >- + uv run --project /home/worsch/repo-manager rmgr workload index --root /home/worsch diff --git a/interfaces/rapp-workload-coverage-v1.yaml b/interfaces/rapp-workload-coverage-v1.yaml new file mode 100644 index 0000000..78ba7b6 --- /dev/null +++ b/interfaces/rapp-workload-coverage-v1.yaml @@ -0,0 +1,50 @@ +apiVersion: helixforge.repo-manager/v1 +kind: OwnerTaskInterface +metadata: + id: helixforge.workloads.rapp-coverage.v1 + title: Clarify universal rapp coverage for managed deployables +source: + repo: repo-manager + workplan_task: RMGR-WP-0010-T05 +target: + repo: railiance-master + owner_agent: railiance-master + suggested_work_record: owner-selected Railiance Master schema workplan task +approval: + dispositions: [approved, amended, rejected] + record_in: Railiance Master file-backed workplan task or decision +task: + title: Clarify universal rapp coverage for managed deployables + priority: high + objective: >- + Make the normative rapp contract explicit that every managed running + deployable, including operational and tooling workloads participating in + controls, belongs to exactly one rapp. + inputs: + - ref: railiance-master/docs/adr/ADR-0007-rapp-declaration-contract.md + authority: railiance-master + - ref: repo-manager/docs/adr-004-authoritative-workload-declarations.md + authority: repo-manager boundary ruling + deliverables: + - >- + Amend or clarify ADR-0007 and the rapp authoring guide with universal + managed-deployable coverage. + - >- + State that a pre-rapp running deployable is migration debt and remains + unknown to workload controls until declared. + - >- + Preserve native identity for human access, credential patterns, broker + actions, one-shot operations, and infrastructure resources that are not workloads. + - Keep Railiance Master as the sole owner of the normative rapp schema vocabulary. + constraints: + - Do not create a parallel generic workload declaration surface. + - Do not require one rapp per process when deployables share rollout and rollback fate. + - Do not turn non-workload operational subjects into fake workloads. + acceptance: + - Normative documentation answers whether operational and tooling deployables require rapps. + - The exactly-one-rapp invariant remains explicit and schema-compatible. + - Migration debt and not-applicable subjects are distinguishable. + - Existing valid rapp declarations continue to validate. + verification: + - tools/validate-family-declarations.py --repo /home/worsch/rapp-issue-core + - tools/validate-family-declarations.py --repo /home/worsch/rapp-qonto diff --git a/interfaces/workload-projection-transport-v1.yaml b/interfaces/workload-projection-transport-v1.yaml new file mode 100644 index 0000000..c7cbad2 --- /dev/null +++ b/interfaces/workload-projection-transport-v1.yaml @@ -0,0 +1,47 @@ +apiVersion: helixforge.repo-manager/v1 +kind: OwnerTaskInterface +metadata: + id: helixforge.workloads.projection-transport.v1 + title: Transport the authoritative workload projection +source: + repo: repo-manager + workplan_task: RMGR-WP-0010-T04 +target: + repo: hub-core + owner_agent: hub-core + suggested_work_record: owner-selected hub-core projection workplan task +approval: + dispositions: [approved, amended, rejected] + record_in: hub-core file-backed workplan task or decision +task: + title: Transport the Repo Manager workload projection + priority: medium + objective: >- + Add a hub-core projection port for Repo Manager workload records without + transferring semantic authority or exposing Repo Manager persistence. + inputs: + - ref: repo-manager/docs/workload-reference-contract_v1.md + authority: repo-manager + - ref: repo-manager/src/repo_manager/workloads.py + authority: repo-manager + deliverables: + - >- + Ingest normalized workload records keyed by rapp_id and + workload_identity.name, with optional deployable membership. + - >- + Retain source repository, declaration path, source Git revision, and a + canonical UTC observation timestamp. + - Expose read-only cross-repository query through the existing projection port. + - Make the projection rebuildable from authoritative declarations. + constraints: + - hub-core transports and queries the projection but does not own workload semantics. + - Do not expose or depend on Repo Manager private persistence models. + - Do not infer workload identity from repository names or paths. + acceptance: + - A nine-declaration Repo Manager fixture can be ingested and queried by exact reference. + - Deleting and rebuilding the projection from source yields equivalent records. + - Unknown references remain unknown and are not repaired by aliases or path parsing. + - All observation timestamps are canonical UTC. + verification: + - make test + - make conformance diff --git a/interfaces/zone-engine-workload-resolution-v1.yaml b/interfaces/zone-engine-workload-resolution-v1.yaml new file mode 100644 index 0000000..9a9090d --- /dev/null +++ b/interfaces/zone-engine-workload-resolution-v1.yaml @@ -0,0 +1,49 @@ +apiVersion: helixforge.repo-manager/v1 +kind: OwnerTaskInterface +metadata: + id: helixforge.workloads.zone-engine-resolution.v1 + title: Consume explicit workload references without inference +source: + repo: repo-manager + workplan_task: RMGR-WP-0010-T07 +target: + repo: zone-engine + owner_agent: zone-engine + suggested_work_record: ZONE-WP-0001-T07 +approval: + dispositions: [approved, amended, rejected] + record_in: ZONE-WP-0001-T07 or a Zone Engine owner decision +task: + title: Prove Zone Engine workload resolution without inference + priority: high + objective: >- + Join ops-warden lane pointers to authoritative Repo Manager workload + projections and expose unresolved workload identity without guessing. + inputs: + - ref: repo-manager/docs/workload-reference-contract_v1.md + authority: repo-manager + - ref: helixforge.workloads.ops-warden-reference.v1 + authority: ops-warden after approval + - ref: zone-engine/docs/security-zones_v0.1.md + authority: zone-engine draft pending canon adoption + deliverables: + - Consume exact rapp_id and workload name pairs plus optional deployable. + - Preserve resolved, unknown, and owner-asserted not-applicable as distinct states. + - Join authoritative projection data to explicit catalog references only. + - Emit evidence that name and path drift cannot change zone membership. + constraints: + - Do not parse repository slugs, owner_repo, credential paths, or path templates as identity. + - Unknown must fail closed wherever zone policy requires workload identity. + - Zone Engine does not author workload identity or ops-warden lane pointers. + acceptance: + - A valid exact reference resolves to one authoritative workload projection. + - A mismatched qonto versus rapp-qonto name remains unknown. + - A non-workload lane remains not-applicable rather than becoming unknown. + - No fallback identity path exists in implementation or fixtures. + verification: + - >- + uv run --project /home/worsch/repo-manager rmgr workload resolve --root + /home/worsch --rapp-id rapp-issue-core --name issue-core --deployable issue-core + - >- + uv run --project /home/worsch/repo-manager rmgr workload resolve --root + /home/worsch --rapp-id rapp-qonto --name rapp-qonto diff --git a/schemas/owner-task-interface.schema.yaml b/schemas/owner-task-interface.schema.yaml new file mode 100644 index 0000000..7b24e3a --- /dev/null +++ b/schemas/owner-task-interface.schema.yaml @@ -0,0 +1,72 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: https://helixforge.local/repo-manager/owner-task-interface.schema.yaml +title: HelixForge owner-task interface +type: object +additionalProperties: false +required: [apiVersion, kind, metadata, source, target, approval, task] +properties: + apiVersion: + const: helixforge.repo-manager/v1 + kind: + const: OwnerTaskInterface + metadata: + type: object + additionalProperties: false + required: [id, title] + properties: + id: {type: string, minLength: 1} + title: {type: string, minLength: 1} + source: + type: object + additionalProperties: false + required: [repo, workplan_task] + properties: + repo: {type: string, minLength: 1} + workplan_task: {type: string, minLength: 1} + target: + type: object + additionalProperties: false + required: [repo, owner_agent] + properties: + repo: {type: string, minLength: 1} + owner_agent: {type: string, minLength: 1} + suggested_work_record: {type: string, minLength: 1} + approval: + type: object + additionalProperties: false + required: [dispositions, record_in] + properties: + dispositions: + const: [approved, amended, rejected] + record_in: {type: string, minLength: 1} + task: + type: object + additionalProperties: false + required: [title, priority, objective, deliverables, acceptance] + properties: + title: {type: string, minLength: 1} + priority: {enum: [high, medium, low]} + objective: {type: string, minLength: 1} + inputs: + type: array + items: + type: object + additionalProperties: false + required: [ref, authority] + properties: + ref: {type: string, minLength: 1} + authority: {type: string, minLength: 1} + deliverables: + type: array + minItems: 1 + items: {type: string, minLength: 1} + constraints: + type: array + items: {type: string, minLength: 1} + acceptance: + type: array + minItems: 1 + items: {type: string, minLength: 1} + verification: + type: array + items: {type: string, minLength: 1} diff --git a/src/repo_manager/cli.py b/src/repo_manager/cli.py index c33b184..ffa956b 100644 --- a/src/repo_manager/cli.py +++ b/src/repo_manager/cli.py @@ -382,6 +382,21 @@ def main(argv: list[str] | None = None) -> int: p_workload_resolve.add_argument("--name", required=True) p_workload_resolve.add_argument("--deployable", default=None) + p_owner_interface = sub.add_parser( + "owner-interface", + help="Inspect validated owner-consumable task interfaces", + ) + owner_interface_sub = p_owner_interface.add_subparsers(dest="owner_interface_command") + p_owner_interface_validate = owner_interface_sub.add_parser( + "validate", help="Validate and return owner task interfaces" + ) + p_owner_interface_validate.add_argument( + "--path", default="interfaces", help="One interface YAML or an interface directory" + ) + p_owner_interface_validate.add_argument( + "--owner", default=None, help="Return interfaces for this target repo or owner agent" + ) + args = parser.parse_args(argv) if args.version or args.command in (None, "version"): @@ -847,6 +862,16 @@ 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 == "owner-interface": + if not args.owner_interface_command: + p_owner_interface.print_help() + return 2 + from repo_manager.owner_interfaces import validate_owner_interfaces + + result = validate_owner_interfaces(Path(args.path), owner=args.owner) + print(json.dumps(result, indent=2)) + return 0 if result.get("ok") else 1 + parser.print_help() return 0 diff --git a/src/repo_manager/owner_interfaces.py b/src/repo_manager/owner_interfaces.py new file mode 100644 index 0000000..86d1669 --- /dev/null +++ b/src/repo_manager/owner_interfaces.py @@ -0,0 +1,177 @@ +"""Validation and rendering for owner-consumable task interfaces.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import yaml + +API_VERSION = "helixforge.repo-manager/v1" +KIND = "OwnerTaskInterface" +DISPOSITIONS = ("approved", "amended", "rejected") +PRIORITIES = frozenset({"high", "medium", "low"}) + + +def _error(code: str, path: Path, message: str) -> dict[str, str]: + return {"code": code, "path": str(path), "message": message} + + +def _text(value: Any) -> str | None: + if not isinstance(value, str): + return None + value = value.strip() + return value or None + + +def _string_list(value: Any) -> list[str] | None: + if not isinstance(value, list) or not value: + return None + values = [_text(item) for item in value] + if any(item is None for item in values): + return None + return [item for item in values if item is not None] + + +def _interface_paths(path: Path) -> list[Path]: + path = path.expanduser().resolve() + if path.is_file(): + return [path] + return sorted(path.glob("*.yaml")) + + +def validate_owner_interfaces(path: Path, *, owner: str | None = None) -> dict[str, Any]: + """Validate task interfaces and return their directly consumable content.""" + path = path.expanduser().resolve() + errors: list[dict[str, str]] = [] + interfaces: list[dict[str, Any]] = [] + seen_ids: set[str] = set() + + paths = _interface_paths(path) + if not paths: + errors.append(_error("interface_not_found", path, "no interface YAML files found")) + + for interface_path in paths: + try: + raw = yaml.safe_load(interface_path.read_text(encoding="utf-8")) or {} + except (OSError, yaml.YAMLError) as exc: + errors.append(_error("interface_invalid", interface_path, str(exc))) + continue + if not isinstance(raw, dict): + errors.append(_error("interface_invalid", interface_path, "root must be a mapping")) + continue + if raw.get("apiVersion") != API_VERSION or raw.get("kind") != KIND: + errors.append( + _error( + "interface_kind_invalid", + interface_path, + f"expected apiVersion {API_VERSION!r} and kind {KIND!r}", + ) + ) + continue + + metadata = raw.get("metadata") + source = raw.get("source") + target = raw.get("target") + approval = raw.get("approval") + task = raw.get("task") + if not all(isinstance(item, dict) for item in (metadata, source, target, approval, task)): + errors.append( + _error( + "interface_shape_invalid", + interface_path, + "metadata, source, target, approval, and task must be mappings", + ) + ) + continue + + interface_id = _text(metadata.get("id")) + title = _text(metadata.get("title")) + source_repo = _text(source.get("repo")) + source_task = _text(source.get("workplan_task")) + target_repo = _text(target.get("repo")) + owner_agent = _text(target.get("owner_agent")) + task_title = _text(task.get("title")) + objective = _text(task.get("objective")) + priority = _text(task.get("priority")) + deliverables = _string_list(task.get("deliverables")) + acceptance = _string_list(task.get("acceptance")) + dispositions = approval.get("dispositions") + + required = { + "metadata.id": interface_id, + "metadata.title": title, + "source.repo": source_repo, + "source.workplan_task": source_task, + "target.repo": target_repo, + "target.owner_agent": owner_agent, + "task.title": task_title, + "task.objective": objective, + } + missing = [name for name, value in required.items() if value is None] + if missing: + errors.append( + _error( + "interface_required_field_missing", + interface_path, + "missing non-empty fields: " + ", ".join(missing), + ) + ) + continue + if priority not in PRIORITIES: + errors.append( + _error( + "interface_priority_invalid", + interface_path, + "task.priority must be high, medium, or low", + ) + ) + continue + if deliverables is None or acceptance is None: + errors.append( + _error( + "interface_task_invalid", + interface_path, + "task.deliverables and task.acceptance must be non-empty string lists", + ) + ) + continue + if dispositions != list(DISPOSITIONS): + errors.append( + _error( + "interface_approval_invalid", + interface_path, + "approval.dispositions must be [approved, amended, rejected]", + ) + ) + continue + if "status" in raw or "status" in metadata: + errors.append( + _error( + "interface_lifecycle_forbidden", + interface_path, + "an interface is not a work record and must not carry lifecycle status", + ) + ) + continue + if interface_id in seen_ids: + errors.append( + _error("interface_id_duplicate", interface_path, f"duplicate id {interface_id!r}") + ) + continue + seen_ids.add(interface_id) + + if owner is None or owner in {target_repo, owner_agent}: + document = dict(raw) + document["path"] = str(interface_path) + interfaces.append(document) + + return { + "ok": not errors, + "apiVersion": API_VERSION, + "kind": KIND, + "root": str(path), + "interface_count": len(interfaces), + "interfaces": interfaces, + "errors": errors, + } diff --git a/tests/test_owner_interfaces.py b/tests/test_owner_interfaces.py new file mode 100644 index 0000000..a427fc8 --- /dev/null +++ b/tests/test_owner_interfaces.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from pathlib import Path + +from repo_manager.owner_interfaces import validate_owner_interfaces + + +def _write_interface(path: Path, *, interface_id: str = "example.port.v1") -> None: + path.write_text( + f"""apiVersion: helixforge.repo-manager/v1 +kind: OwnerTaskInterface +metadata: + id: {interface_id} + title: Example port +source: + repo: repo-manager + workplan_task: RMGR-WP-0010-T04 +target: + repo: hub-core + owner_agent: hub-core +approval: + dispositions: [approved, amended, rejected] + record_in: target owner work record +task: + title: Adopt the example port + priority: medium + objective: Consume the published contract without copying its authority. + deliverables: + - Add the consumer port. + acceptance: + - Prove an authoritative record can be consumed. +""", + encoding="utf-8", + ) + + +def test_validates_and_filters_owner_consumable_interface(tmp_path: Path) -> None: + _write_interface(tmp_path / "example.yaml") + + result = validate_owner_interfaces(tmp_path, owner="hub-core") + + assert result["ok"] is True + assert result["interface_count"] == 1 + assert result["interfaces"][0]["metadata"]["id"] == "example.port.v1" + + +def test_valid_interface_for_another_owner_is_not_returned(tmp_path: Path) -> None: + _write_interface(tmp_path / "example.yaml") + + result = validate_owner_interfaces(tmp_path, owner="ops-warden") + + assert result["ok"] is True + assert result["interfaces"] == [] + + +def test_rejects_lifecycle_status_because_interface_is_not_work_record(tmp_path: Path) -> None: + path = tmp_path / "example.yaml" + _write_interface(path) + path.write_text(path.read_text(encoding="utf-8") + "status: wait\n", encoding="utf-8") + + result = validate_owner_interfaces(path) + + assert result["ok"] is False + assert result["errors"][0]["code"] == "interface_lifecycle_forbidden" + + +def test_rejects_duplicate_interface_ids(tmp_path: Path) -> None: + _write_interface(tmp_path / "one.yaml") + _write_interface(tmp_path / "two.yaml") + + result = validate_owner_interfaces(tmp_path) + + assert result["ok"] is False + assert result["errors"][0]["code"] == "interface_id_duplicate" diff --git a/workplans/RMGR-WP-0010-authoritative-workload-references.md b/workplans/RMGR-WP-0010-authoritative-workload-references.md index d7d0ae4..960aaca 100644 --- a/workplans/RMGR-WP-0010-authoritative-workload-references.md +++ b/workplans/RMGR-WP-0010-authoritative-workload-references.md @@ -4,7 +4,7 @@ type: workplan title: "Authoritative workload declarations and explicit references" domain: infotech repo: repo-manager -status: active +status: finished owner: codex topic_slug: infotech created: "2026-08-22" @@ -13,6 +13,7 @@ related: - ZONE-WP-0001 - RMASTER-ADR-0007 - WARDEN-WP-0032 + - RMGR-ADR-005 state_hub_workstream_id: "f1fdfd70-76e0-5133-8365-4e68b9e9ca12" --- @@ -73,70 +74,80 @@ 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 +## Publish the normalized projection owner interface ```task id: RMGR-WP-0010-T04 -status: wait +status: done priority: medium state_hub_task_id: "187fed63-63ab-57b3-92cb-83acf5870977" ``` -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. +Supply hub-core with a directly adoptable task interface for the v1 projection, +including provenance, canonical UTC observation time, rebuild semantics, and +the private-persistence boundary. -**Handoff (2026-08-22):** sent hub-core message -`1ea93859-7cdf-4c8e-86d6-003e93e70eef`; waiting for its owner workplan/task -identifier. +**Result (2026-08-22):** +`interfaces/workload-projection-transport-v1.yaml` is the complete owner-ready +interface. Initial routing message: +`1ea93859-7cdf-4c8e-86d6-003e93e70eef`. -## Add authoritative estate coverage +## Publish the authoritative estate-coverage owner interface ```task id: RMGR-WP-0010-T05 -status: wait +status: done priority: high state_hub_task_id: "71a4dae7-8484-57f7-96b9-c2ef93aa4642" ``` -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. +Supply Railiance Master with a directly adoptable schema-owner task clarifying +universal managed-deployable coverage, migration debt, and the non-workload +boundary. -**Handoff (2026-08-22):** sent Railiance Master message -`d8e64195-9117-4439-bc67-d2e7696a9bf5` for the normative coverage clarification -and an owner work record. Runtime observation-source selection remains open. +**Result (2026-08-22):** `interfaces/rapp-workload-coverage-v1.yaml` is the +complete owner-ready interface. Initial routing message: +`d8e64195-9117-4439-bc67-d2e7696a9bf5`. Runtime observation-source selection +belongs in the adopting owner work record, not as a duplicate Repo Manager todo. -## Adopt explicit ops-warden references +## Publish the ops-warden reference owner interface ```task id: RMGR-WP-0010-T06 -status: wait +status: done priority: high state_hub_task_id: "72aa51a0-2a06-571c-967d-fb18ee96b30f" ``` -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. +Supply ops-warden with the exact catalog adoption task, including field +semantics, owner boundary, validation, and no-inference acceptance tests. -**Handoff (2026-08-22):** sent ops-warden message -`ca18c1ab-540f-4765-89ba-43c068bb49d2`; waiting for its adoption workplan/task -identifier. +**Result (2026-08-22):** +`interfaces/ops-warden-workload-reference-v1.yaml` is directly adoptable by +`WARDEN-WP-0032`. Initial routing message: +`ca18c1ab-540f-4765-89ba-43c068bb49d2`. -## Prove Zone Engine consumption +## Publish the Zone Engine consumption owner interface ```task id: RMGR-WP-0010-T07 -status: wait +status: done priority: high state_hub_task_id: "b68f40b1-cf63-589f-96ee-c6c1098d9802" ``` -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. +Supply Zone Engine with a directly adoptable join-and-proof task that consumes +the declaration projection and explicit catalog references, reports unknown +subjects, and prohibits path/repository-name inference. -**Handoff (2026-08-22):** replied through Zone Engine message -`a271dc75-b2c9-4c9a-a91c-20aaa1cfb5dc`; waiting for its adoption/proof task -identifier. +**Result (2026-08-22):** +`interfaces/zone-engine-workload-resolution-v1.yaml` is directly adoptable by +`ZONE-WP-0001-T07`, which already links `RMGR-WP-0010-T07`. Initial routing +message: `a271dc75-b2c9-4c9a-a91c-20aaa1cfb5dc`. + +## Completion + +The Repo Manager boundary is complete. `RMGR-ADR-005` makes interface +publication the producer-side terminal action: approval, amendment, and +implementation live only in each target owner's file-backed work record. No +cross-owner implementation todo remains duplicated in this workplan.