Specify prj- drivers and cross-owner wait mode.
Some checks failed
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
check / test (push) Has been cancelled

COORDINATION-WP-0005: map project-repository workplans onto
CoordinationCase, refuse residual as a driver, and document the wait
mode without enabling live worker injection.

Assistant: grok
Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
This commit is contained in:
tegwick 2026-09-14 16:46:45 +02:00
parent b69839b5ef
commit d2802fc55d
9 changed files with 582 additions and 6 deletions

View file

@ -2,7 +2,7 @@
## Repo Identity
**Purpose:** Coordination Engine - (fill in purpose)
**Purpose:** Goal-driven digital coordination; project-repository workplans as cross-repo drivers; worker leases/receipts (not live unless the operator started the service).
**Domain:** communication
**Repo slug:** coordination-engine
@ -27,6 +27,18 @@ Queueable writes return an explicit queued receipt if the central hub is
unreachable. Treat that as pending local evidence, then ask the operator to run
statehub outbox status/replay after connectivity returns.
### Cross-repo waits (COORDINATION-WP-0005)
Do not mint a stranded `wait` when another repo/owner is the blocker.
1. Look for a `prj-` workplan that lists both repos (`spec/coordination-model-v0.2.md`). That plan is the **driver**; product repos **implement**.
2. Observe the foreign record on State Hub. Do not copy or PATCH the other owner's tasks.
3. `flavor: residual` is not a driver and not implementation demand unless promoted.
4. Lease/wakeup only via the v0.1 worker service for selected gita repos. This session does **not** enable live worker injection.
5. Do not message `ops-warden` for secrets (SSH certs only).
Mode: `spec/cross-owner-wait-mode-v0.1.md`.
### Orient at session start
```bash

View file

@ -18,6 +18,11 @@ Installation does not enable a service or start workers. See the
[runbook](docs/worker-runtime.md) for configuration, worker acknowledgements,
checkpoint continuation, backups and local verification.
Project-repository workplans (`prj-`) are coordination **drivers**; product
repos implement. See [v0.2](spec/coordination-model-v0.2.md) and
[cross-owner wait mode](spec/cross-owner-wait-mode-v0.1.md). Residual flavor
is not a driver.
The [v0.1 contract](spec/worker-coordination-service-v0.1.md) and
[adapter decision](docs/adr/002-worker-runtime-boundary.md) describe the ownership
boundary. The authoritative transport contract lives in

View file

@ -14,6 +14,8 @@
* `ProductRequirementsDocument.md`
* `RuntimeArchitectureAndAdapterSubsystem.md`
* `AdapterInterfaceSpecification.md`
* `coordination-model-v0.2.md` — project-repository drivers (COORDINATION-WP-0005)
* `cross-owner-wait-mode-v0.1.md`
## 2. Purpose

View file

@ -0,0 +1,123 @@
# coordination-model-v0.2.md
## 1. Document Status
**Document:** coordination-model-v0.2.md
**Project:** coordination-engine
**Version:** 0.2
**Status:** Addendum to v0.1 (COORDINATION-WP-0005)
**Scope:** Project-repository workplans as coordination drivers
**Extends:** `spec/coordination-model-v0.1.md`
**Related:** `spec/cross-owner-wait-mode-v0.1.md`,
`spec/worker-coordination-service-v0.1.md`,
`the-custodian/canon/architecture/adr-005-cross-repo-workplans-project-repos.md`,
`the-custodian/canon/standards/project-repository-flavor_v0.1.md`
v0.1 remains the ontology. This addendum maps **estate work records** onto
that ontology. It does not replace actors, goals, commitments, or observation
loops.
## 2. Purpose
Cross-repository work in this estate is supposed to live in a **project
repository** (`prj-<slug>`, `repo_flavor: project`, ADR-005). Agents were
instead minting stranded `wait` notes on product workplans. The engine must
treat a `prj-` workplan as the **driver** of a `CoordinationCase`, and
product-repo workplans as **implementers**.
## 3. Out of scope
- The engine **does not author workplans**. Files remain source of truth
(ADR-001). State Hub remains the work-record read model.
- The engine **does not replace State Hub**, Fabric, or product
implementation in participating repos.
- The engine **does not vend credentials**. `ops-warden` issues SSH
certificates only.
- This addendum **does not enable live worker injection**. Wake/lease
remains the v0.1 worker-coordination service, off until an operator
starts it.
## 4. Driver and implementer
| Role | Binding | Authority |
| --- | --- | --- |
| **Driver** | Workplan whose repo is `prj-*` / `category: project` / `repo_flavor: project` | Sequences the effort, names participating repos, holds `depends_on` and related child ids. Does not implement production code. |
| **Implementer** | Workplan in a participating product/tooling repo | Owns code, tests, deployables, and local tasks. |
Participant roles on the case (v0.1 `Participant.roles[]`):
- `driver` — the project-repository actor (system: State Hub record +
project repo)
- `implementer` — each participating product repo (system)
- `observer` — State Hub / Fabric (read-only)
- `agent` — a worker only when the v0.1 service has selected that gita
repo as a wake target
A workplan with `flavor: residual` is **not** a driver and is **not**
actionable demand unless promoted (STATE-WP-0092). Unspecified flavor is
relevant.
## 5. Mapping onto v0.1
```text
prj- workplan → CoordinationCase
goal ← GOAL.md outcome + workplan title
driver ← the prj- workplan (commitment: sequence the effort)
implementers ← child workplans in other repos (related / depends_on)
commitments ← depends_on edges (kind: response | completion)
observations ← State Hub task/workplan status, receipts
```
`depends_on` is a **commitment**, not prose. The obligor is the
implementer (or upstream driver) named by the target id. The beneficiary
is the waiting plan. Satisfaction is the target reaching a terminal
satisfactory status (`done` / `finished` / `archived`), matching the
v0.1 worker-coordination dependency rule.
`related:` without `depends_on` is context, not a commitment.
## 6. Case identity
```yaml
CoordinationCase:
id: "case:prj:{repo_slug}:{workplan_id}" # derived, not authored
scenario_type: project_driver
driver_workplan_id: string # hub UUID or canonical id
driver_repo: prj-*
implementers:
- repo: string
workplan_id: string?
role: implementer
commitments: # from depends_on
- kind: completion
obligor_workplan_id: string
beneficiary_workplan_id: string
flavor: planning | implementation | ... # driver's flavor
```
The engine derives this object from hub JSON. It does not POST a
workplan.
## 7. Worked example — `prj-helixforge-factory` / `HFACT-WP-0001`
Repo `prj-helixforge-factory` is `repo_flavor: project`. Workplan
`HFACT-WP-0001` (“Establish the internal HelixForge software factory on
Railiance”) is the driver. It states that it owns integration acceptance
and handoff and **neither replaces nor copies child implementation
plans**.
Implementers named in `related:` include `GLAS-WP-0012`, `SAND-WP-0015`,
`REINAH-WP-0003`, `ACTIVITY-WP-0032`, `KEY-WP-0013`, `INFD-WP-0001`,
`APPROVAL-WP-0002`, `SECRETS-WP-0009`, `AUDIT-WP-0009`, `RPF-WP-0038`.
Those remain implementer workplans in their owning repos.
A wait on `KEY-WP-0013` from the factory driver is a **commitment** on
the key-cape implementer, not a stranded `wait` on the driver with no
observer. Cross-owner handling is `spec/cross-owner-wait-mode-v0.1.md`.
## 8. Compatibility
v0.1 cases that are not `scenario_type: project_driver` are unchanged.
The worker-coordination service continues to poll ready/active
implementer tasks. This addendum tells it (and agents) which workplan is
the driver when one exists.

View file

@ -0,0 +1,109 @@
# Cross-owner wait handling mode v0.1
Status: specified (COORDINATION-WP-0005-T02). Not a live wake enablement.
Owner: coordination-engine.
Reconciles with: `spec/worker-coordination-service-v0.1.md` (do not fork
leases, safety, TAMQ, or receipts).
Ontology: `spec/coordination-model-v0.2.md`.
## Problem
Repo A sets a task `wait` because repo Bs owner has not returned. Nobody
observes B. The wait becomes a stranded inbox. That pattern is the top
theme holding the open-workplan count up.
## Mode (replace “set wait and hope”)
### Entry
Any of:
1. A task is `wait` / `needs_human` and names another repo, owner, or
workplan id.
2. A workplan is `blocked` on a `depends_on` target whose `repo` is not
this repo.
3. A `prj-` driver lists both repos (preferred). If no driver exists,
still open a **case object** from the waiting plan — do not author a
new workplan.
Do **not** enter this mode for `flavor: residual` unless the flavor has
been promoted.
### Observe
Read the foreign owners record from State Hub (workplan, tasks,
`depends_on`, flavor, repo classification). Do not copy their task list
into this repo. Do not PATCH their tasks.
### Attach
Open or attach a `CoordinationCase` (`scenario_type: project_driver`
when a `prj-` workplan exists, otherwise `scenario_type: cross_owner_wait`).
The waiting plan is the beneficiary. The foreign plan is the obligor
commitment.
### Act (only if the v0.1 service is already running)
Lease/wakeup **only** selected gita-registered worker targets for the
implementer that is actually unblocked. Safety, sensitive-text stops,
human flags, and “no silent reroute” are exactly the v0.1 worker service.
This mode does not add a second wakeup path.
If the service is not running, the case still exists as a derived object
and a hub-projectable receipt of `observed` / `waiting`. Agents follow
the instruction pack instead of injecting workers.
### Checkpoint
Reuse v0.1 checkpoint shape (summary, files_changed, next_action,
blocked_reason, 8 KiB, local). `blocked_reason` stops automatic
continuation. A checkpoint is not completion.
### Fail closed
Missing authorization, unregistered target, residual-as-driver, or
attempt to reassign another owners task → `stopped` with reason. Never
silently re-assign.
### Exit
| Exit | Condition |
| --- | --- |
| `satisfied` | Obligor workplan/task reaches `done`/`finished`/`archived` |
| `stopped` | Safety, human, missing auth, residual driver |
| `waiting` | Foreign record still open; case remains attached |
## Receipt shape (hub-projectable)
Extends the v0.1 sanitized receipt (state, reason, lease/trigger ids).
Additional fields (no payloads, no secrets):
```yaml
scenario_type: project_driver | cross_owner_wait
case_id: string
driver_workplan_id: string?
driver_repo: string?
obligor_workplan_id: string
beneficiary_workplan_id: string
flavor: string?
```
Projection remains `POST /progress/` with
`event_type: coordination_receipt` as in the v0.1 Hub adapter. Queued
edge-relay receipts are not proof of central publication.
## Common estate pattern
Repo A (e.g. `net-kingdom`) blocked on repo B (e.g. `key-cape`,
`flex-auth`, OpenBao owner). If `HFACT-WP-0001` (or another `prj-`
driver) already lists both, attach there. Otherwise derive
`cross_owner_wait` from As waiting workplan. Observe B. Do not implement
Bs tasks from As session. Do not message `ops-warden` for a secret.
## Anti-patterns
- Stranded `wait` with no case and no observer
- Copying Bs tasks into As workplan
- Treating `flavor: residual` as a driver
- Inventing `warden secret` / asking ops-warden for API keys
- Enabling live worker injection as a side effect of documenting this mode

View file

@ -12,6 +12,11 @@ that repository. This package installs `coordination-engine`, never `tamq`.
## Observation and actionability
Cross-owner waits that used to become stranded `wait` notes are specified in
`spec/cross-owner-wait-mode-v0.1.md`. That mode **reuses** this service's
observation, leases, safety gates and receipts; it does not add a second
wakeup path or enable live injection.
The runtime polls State Hub's repos, workplans, tasks, dependency edges and unread
messages. It dispatches only after a complete successful snapshot, only for
explicitly configured gita repositories. It refreshes gita before addressing a

View file

@ -0,0 +1,205 @@
"""Derive a project-driver CoordinationCase from hub-shaped JSON.
Does not author workplans, wake workers, or treat residual flavor as demand.
"""
from __future__ import annotations
from typing import Any
RESIDUAL = "residual"
PROJECT_CATEGORIES = frozenset({"project"})
OPEN_PLANS = frozenset({"proposed", "ready", "active", "blocked", "backlog"})
SATISFIED = frozenset({"done", "finished", "archived", "cancel"})
def repo_is_project(repo: dict[str, Any] | None) -> bool:
if not repo:
return False
slug = str(repo.get("slug") or "")
category = str(
repo.get("category")
or (repo.get("repo_classification") or {}).get("category")
or ""
)
flavor = str(repo.get("repo_flavor") or "")
return slug.startswith("prj-") or category in PROJECT_CATEGORIES or flavor == "project"
def workplan_is_residual(plan: dict[str, Any] | None) -> bool:
if not plan:
return False
return str(plan.get("flavor") or "").strip().lower() == RESIDUAL
def _index_repos(snapshot: dict[str, Any]) -> dict[str, dict[str, Any]]:
repos = snapshot.get("repos") or {}
if isinstance(repos, dict):
# Hub adapter snapshot uses {repo_id: slug}. Tests may pass full rows.
out: dict[str, dict[str, Any]] = {}
for key, value in repos.items():
if isinstance(value, dict):
out[str(value.get("id") or key)] = value
else:
out[str(key)] = {"id": key, "slug": value}
return out
return {str(row.get("id")): row for row in repos if isinstance(row, dict) and row.get("id")}
def _plans(snapshot: dict[str, Any]) -> dict[str, dict[str, Any]]:
rows = snapshot.get("plans") or snapshot.get("workplans") or []
return {str(row["id"]): row for row in rows if isinstance(row, dict) and row.get("id")}
def _depends_targets(plan: dict[str, Any]) -> list[str]:
raw = plan.get("depends_on")
if raw is None:
raw = plan.get("depends_on_workplans")
if raw is None:
return []
if not isinstance(raw, list):
raw = [raw]
out: list[str] = []
for item in raw:
if isinstance(item, dict):
target = item.get("workplan_id") or item.get("workstream_id") or item.get("id")
else:
target = item
if target:
out.append(str(target))
return out
def _related_ids(plan: dict[str, Any]) -> list[str]:
raw = plan.get("related") or plan.get("related_workplans") or []
if isinstance(raw, str):
return [raw]
return [str(item) for item in raw]
def _canonical(plan: dict[str, Any]) -> str:
return str(plan.get("record_id") or plan.get("slug") or plan.get("id") or "")
def _match_plan(plans: dict[str, dict[str, Any]], token: str) -> dict[str, Any] | None:
if token in plans:
return plans[token]
needle = token.lower().replace("_", "-")
for plan in plans.values():
slug = str(plan.get("slug") or "").lower().replace("_", "-")
record = str(plan.get("record_id") or "").lower()
if slug == needle or record == needle or slug.upper() == token.upper():
return plan
if str(plan.get("id")) == token:
return plan
return None
class ResidualDriverError(ValueError):
"""Raised when a residual workplan is asked to drive a case."""
class NotProjectDriverError(ValueError):
"""Raised when the selected workplan is not in a project repository."""
def build_driver_case(
snapshot: dict[str, Any],
workplan_id: str | None = None,
) -> dict[str, Any]:
"""Build a derived CoordinationCase from hub JSON.
Residual flavor is refused as a driver. Non-project workplans are refused
as drivers (use cross_owner_wait instead).
"""
repos = _index_repos(snapshot)
plans = _plans(snapshot)
if workplan_id:
plan = _match_plan(plans, workplan_id)
if plan is None:
raise KeyError(f"workplan not in snapshot: {workplan_id}")
else:
plan = next(
(
row
for row in plans.values()
if str(row.get("status") or "") in OPEN_PLANS
and repo_is_project(repos.get(str(row.get("repo_id"))))
and not workplan_is_residual(row)
),
None,
)
if plan is None:
raise NotProjectDriverError("no open project-repository workplan in snapshot")
if workplan_is_residual(plan):
raise ResidualDriverError("residual flavor is not a coordination driver")
repo = repos.get(str(plan.get("repo_id")))
if not repo_is_project(repo):
raise NotProjectDriverError("workplan repo is not a project repository")
repo_slug = str((repo or {}).get("slug") or "")
driver_id = str(plan["id"])
implementers = []
seen_repos: set[str] = set()
commitments = []
for token in _depends_targets(plan):
target = _match_plan(plans, token)
commitments.append(
{
"kind": "completion",
"obligor_workplan_id": str(target["id"]) if target else token,
"beneficiary_workplan_id": driver_id,
"state": (
"satisfied"
if target and str(target.get("status") or "") in SATISFIED
else "active"
),
}
)
if target:
t_repo = repos.get(str(target.get("repo_id")))
slug = str((t_repo or {}).get("slug") or "")
if slug and slug not in seen_repos and slug != repo_slug:
seen_repos.add(slug)
implementers.append(
{
"repo": slug,
"workplan_id": str(target["id"]),
"role": "implementer",
}
)
for token in _related_ids(plan):
target = _match_plan(plans, token)
if not target:
continue
t_repo = repos.get(str(target.get("repo_id")))
slug = str((t_repo or {}).get("slug") or "")
if slug and slug not in seen_repos and slug != repo_slug:
seen_repos.add(slug)
implementers.append(
{
"repo": slug,
"workplan_id": str(target["id"]),
"role": "implementer",
}
)
flavor = str(plan.get("flavor") or "").strip().lower() or None
return {
"id": f"case:prj:{repo_slug}:{driver_id}",
"scenario_type": "project_driver",
"driver_workplan_id": driver_id,
"driver_repo": repo_slug,
"flavor": flavor,
"goal": plan.get("title"),
"implementers": implementers,
"commitments": commitments,
"actionable": True,
}
def actionable_demand(plan: dict[str, Any]) -> bool:
"""False for residual flavor. Unspecified flavor is relevant."""
return not workplan_is_residual(plan)

View file

@ -0,0 +1,108 @@
from coordination_engine.project_driver import (
NotProjectDriverError,
ResidualDriverError,
actionable_demand,
build_driver_case,
repo_is_project,
workplan_is_residual,
)
HFACT = {
"repos": {
"prj": {
"id": "prj",
"slug": "prj-helixforge-factory",
"category": "project",
"repo_flavor": "project",
},
"key": {"id": "key", "slug": "key-cape", "category": "product"},
"glas": {"id": "glas", "slug": "glas-harness", "category": "tooling"},
"infd": {"id": "infd", "slug": "informed-decision", "category": "product"},
},
"plans": [
{
"id": "ed4fe524",
"slug": "hfact-wp-0001",
"title": "Establish the internal HelixForge software factory on Railiance",
"status": "active",
"flavor": "implementation",
"repo_id": "prj",
"depends_on": [{"workplan_id": "key-wp"}],
"related": ["GLAS-WP-0012", "INFD-WP-0001", "KEY-WP-0013"],
},
{
"id": "key-wp",
"slug": "key-wp-0013",
"title": "Approval-engine resource audience",
"status": "blocked",
"flavor": "implementation",
"repo_id": "key",
},
{
"id": "glas-wp",
"slug": "glas-wp-0012",
"title": "Prove the first local rein profile",
"status": "blocked",
"repo_id": "glas",
},
{
"id": "infd-wp",
"slug": "infd-wp-0001",
"title": "Founding specs",
"status": "active",
"flavor": "planning",
"repo_id": "infd",
},
{
"id": "leftover",
"slug": "hfact-wp-0099",
"title": "Factory leftover",
"status": "proposed",
"flavor": "residual",
"repo_id": "prj",
},
],
}
def test_hfact_builds_driver_case_with_implementers_and_depends_on_commitments():
case = build_driver_case(HFACT, "ed4fe524")
assert case["scenario_type"] == "project_driver"
assert case["driver_repo"] == "prj-helixforge-factory"
assert case["driver_workplan_id"] == "ed4fe524"
assert case["actionable"] is True
repos = {row["repo"] for row in case["implementers"]}
assert "key-cape" in repos
assert "glas-harness" in repos
assert "informed-decision" in repos
assert "prj-helixforge-factory" not in repos
assert case["commitments"]
assert case["commitments"][0]["kind"] == "completion"
assert case["commitments"][0]["obligor_workplan_id"] == "key-wp"
assert case["commitments"][0]["beneficiary_workplan_id"] == "ed4fe524"
assert case["commitments"][0]["state"] == "active"
def test_residual_is_refused_as_driver_and_is_not_actionable_demand():
leftover = next(p for p in HFACT["plans"] if p["id"] == "leftover")
assert workplan_is_residual(leftover)
assert actionable_demand(leftover) is False
try:
build_driver_case(HFACT, "leftover")
raise AssertionError("residual must not drive a case")
except ResidualDriverError:
pass
def test_product_workplan_is_not_a_project_driver():
assert repo_is_project(HFACT["repos"]["key"]) is False
try:
build_driver_case(HFACT, "key-wp")
raise AssertionError("product plan must not be a driver")
except NotProjectDriverError:
pass
def test_auto_selects_open_project_plan_and_skips_residual():
case = build_driver_case(HFACT)
assert case["driver_workplan_id"] == "ed4fe524"

View file

@ -4,7 +4,7 @@ type: workplan
title: "prj- awareness, cross-owner waits, and agent coordination mode"
domain: communication
repo: coordination-engine
status: proposed
status: finished
owner: grok
topic_slug: communication
flavor: extension
@ -42,7 +42,7 @@ Do not invent credential vending.
```task
id: COORDINATION-WP-0005-T01
status: todo
status: done
priority: high
state_hub_task_id: "de21e1da-e66d-51d4-8689-6017346199bd"
```
@ -63,7 +63,7 @@ existing project repo.
```task
id: COORDINATION-WP-0005-T02
status: todo
status: done
priority: high
depends_on: [COORDINATION-WP-0005-T01]
state_hub_task_id: "9e99e12b-b4a2-569a-bce2-dbdc645d75a1"
@ -85,7 +85,7 @@ service rather than forked.
```task
id: COORDINATION-WP-0005-T03
status: wait
status: done
priority: medium
depends_on: [COORDINATION-WP-0005-T01, STATE-WP-0092]
state_hub_task_id: "2c5d91a5-2e77-5778-ac52-f95ea5c2ed63"
@ -104,7 +104,7 @@ workplan and refuse to treat a residual as actionable demand.
```task
id: COORDINATION-WP-0005-T04
status: wait
status: done
priority: high
depends_on: [COORDINATION-WP-0005-T02]
state_hub_task_id: "967e709e-1fe8-5ee0-a544-41e7cfe13f4b"
@ -124,3 +124,10 @@ residuals; do not message `ops-warden` for secrets. Keep the pack short.
Done when the named files describe the mode with a pointer to the spec,
and a new session can choose “coordinate via engine” without inventing a
parallel process.
2026-09-14: T01T04 landed. Specs:
`spec/coordination-model-v0.2.md`, `spec/cross-owner-wait-mode-v0.1.md`.
Adapter: `coordination_engine.project_driver.build_driver_case` (HFACT
fixture; residual refused). Agent pack in this `AGENTS.md`,
`the-custodian` session protocol, and repo-manager scaffold/rapp
templates. Live worker injection was not enabled.