feat(handoffs): publish owner task interfaces
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
parent
a3952113d5
commit
890f3b05b5
12 changed files with 754 additions and 38 deletions
|
|
@ -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 |
|
||||
|
|
|
|||
70
docs/adr-005-owner-task-interfaces.md
Normal file
70
docs/adr-005-owner-task-interfaces.md
Normal file
|
|
@ -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.
|
||||
92
docs/owner-task-interface-contract_v1.md
Normal file
92
docs/owner-task-interface-contract_v1.md
Normal file
|
|
@ -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: <Git commit containing the interface>
|
||||
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.
|
||||
49
interfaces/ops-warden-workload-reference-v1.yaml
Normal file
49
interfaces/ops-warden-workload-reference-v1.yaml
Normal file
|
|
@ -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
|
||||
50
interfaces/rapp-workload-coverage-v1.yaml
Normal file
50
interfaces/rapp-workload-coverage-v1.yaml
Normal file
|
|
@ -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
|
||||
47
interfaces/workload-projection-transport-v1.yaml
Normal file
47
interfaces/workload-projection-transport-v1.yaml
Normal file
|
|
@ -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
|
||||
49
interfaces/zone-engine-workload-resolution-v1.yaml
Normal file
49
interfaces/zone-engine-workload-resolution-v1.yaml
Normal file
|
|
@ -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
|
||||
72
schemas/owner-task-interface.schema.yaml
Normal file
72
schemas/owner-task-interface.schema.yaml
Normal file
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
177
src/repo_manager/owner_interfaces.py
Normal file
177
src/repo_manager/owner_interfaces.py
Normal file
|
|
@ -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,
|
||||
}
|
||||
74
tests/test_owner_interfaces.py
Normal file
74
tests/test_owner_interfaces.py
Normal file
|
|
@ -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"
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue