Specify prj- drivers and cross-owner wait mode.
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:
parent
b69839b5ef
commit
d2802fc55d
9 changed files with 582 additions and 6 deletions
14
AGENTS.md
14
AGENTS.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
123
spec/coordination-model-v0.2.md
Normal file
123
spec/coordination-model-v0.2.md
Normal 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.
|
||||
109
spec/cross-owner-wait-mode-v0.1.md
Normal file
109
spec/cross-owner-wait-mode-v0.1.md
Normal 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 B’s 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 owner’s 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 owner’s 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 A’s waiting workplan. Observe B. Do not implement
|
||||
B’s tasks from A’s session. Do not message `ops-warden` for a secret.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- Stranded `wait` with no case and no observer
|
||||
- Copying B’s tasks into A’s 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
205
src/coordination_engine/project_driver.py
Normal file
205
src/coordination_engine/project_driver.py
Normal 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)
|
||||
108
tests/test_project_driver.py
Normal file
108
tests/test_project_driver.py
Normal 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"
|
||||
|
|
@ -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: T01–T04 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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue