feat(ACTIVITY-WP-0029): inventory callers, retarget sweep, bound execution
Map every State Hub/core-hub caller to a post-retirement owner. Keep the 15-minute sweep schedule here and point the engine at repo-manager (State Hub dual-run by default, REPO_MANAGER_URL when present). Publish GET /execution/semantics and 410 workplan launch routes so State Hub /execution/* is not re-homed as a task database. T03 still waits on HUB-WP-0004.
This commit is contained in:
parent
df3330f165
commit
f6cfc28c33
12 changed files with 359 additions and 12 deletions
|
|
@ -15,6 +15,12 @@ NATS_URL=nats://nats:4222
|
|||
# ── Service integrations (gracefully degraded if unavailable) ─────────────────
|
||||
# State Hub — used by the state-hub context adapter. Binds {} on failure.
|
||||
STATE_HUB_URL=http://127.0.0.1:8000
|
||||
# Consistency sweep engine is repo-manager (ACTIVITY-WP-0029). Default POST
|
||||
# still hits State Hub dual-run. Set REPO_MANAGER_URL or CONSISTENCY_SWEEP_URL
|
||||
# when repo-manager exposes the HTTP sweep.
|
||||
# REPO_MANAGER_URL=
|
||||
# CONSISTENCY_SWEEP_URL=
|
||||
# CONSISTENCY_SWEEP_PATH=/consistency/sweep/remote-all
|
||||
# Repo scoping — used by the repo-scoping context adapter. Binds {} on failure.
|
||||
REPO_SCOPING_URL=http://127.0.0.1:8020
|
||||
# Issue Core — task emission backend.
|
||||
|
|
|
|||
2
SCOPE.md
2
SCOPE.md
|
|
@ -212,7 +212,7 @@ Open work (SBOM catch-up parked — CUST-WP-0062 / ACTIVITY-WP-0030):
|
|||
| **G8. Live-images hygiene** | Medium (ops) | Multi-cluster `live-images-all.txt` must be refreshed after deploys or prune can delete live tags (incident 2026-07-21, restored). `scripts/refresh_live_images.sh` (T04). |
|
||||
| **G9. Evidence federation** | Low | Progress often lands on railiance01 edge/hub; workstation primary hub may not show the same feed without tunnel/outbox health. |
|
||||
| **G10. API external access** | Done | WP-0025 finished: Traefik + Authelia SSO for ops + Temporal UI; LLDAP `activity-core-operators` (NK-WP-0021); port-forward break-glass only. |
|
||||
| **G11. State Hub as permanent target** | High (retirement) | Sweeps, progress sinks, and `/execution/*` still assume State Hub. **ACTIVITY-WP-0029** retargets to repo-manager + hub-core. Do not add new State Hub authorities. |
|
||||
| **G11. State Hub as permanent target** | Medium (T03 open) | Sweep is dual-run/repo-manager-retargetable; `/execution/semantics` is local. Progress/evidence sinks still wait on HUB-WP-0004 (`ACTIVITY-WP-0029-T03`). |
|
||||
|
||||
### Drift risks
|
||||
|
||||
|
|
|
|||
62
docs/execution-queue-boundary.md
Normal file
62
docs/execution-queue-boundary.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
id: ACTIVITY-WP-0029-T04
|
||||
type: contract
|
||||
title: Execution queue boundary
|
||||
updated: "2026-08-18"
|
||||
---
|
||||
|
||||
# Execution queue boundary
|
||||
|
||||
Replaces State Hub `/execution/*` as the place callers look for *when work
|
||||
should fire*. It does **not** move workplan files, task lifecycle, or ITC
|
||||
Task Model types into activity-core.
|
||||
|
||||
## What activity-core owns
|
||||
|
||||
| Surface | Meaning |
|
||||
| --- | --- |
|
||||
| Temporal Schedule + `POST /activity-definitions/{id}/trigger` | When an ActivityDefinition fires |
|
||||
| `POST /ops-runs/claim` and complete/fail/heartbeat | Claimable **ops run** for that fire (ACT-ADR-005) |
|
||||
| `GET /execution/semantics` | This contract |
|
||||
|
||||
`port.schedule` (hub-extension 0.1) is the hub-core **outbound** port that
|
||||
asks activity-core to wake a schedule or accept a launch of an
|
||||
ActivityDefinition. activity-core implements the receiving side.
|
||||
|
||||
## What activity-core does not own
|
||||
|
||||
| Concern | Owner |
|
||||
| --- | --- |
|
||||
| Workplan / task **files** and their status | The repo (ADR-001) |
|
||||
| Work-record **index** and C-rule consistency | repo-manager (`port.work`) |
|
||||
| Task / Commitment / TaskState vocabulary | info-tech-canon Task Model |
|
||||
| Assign / track / close a development task | issue-core / work records |
|
||||
| Policy publication | policy-nexus |
|
||||
| Workplan `execution_state` rows on State Hub | retiring; do not re-home as hub tables here |
|
||||
|
||||
A workplan being `active` is a **lifecycle** fact in the repo file. An
|
||||
ops_run being `open` is an **automation fire**. Those are different planes
|
||||
(ACT-ADR-005). Mapping State Hub `POST /execution/launch-requests` onto
|
||||
“create a workplan execution row here” would collapse them again.
|
||||
|
||||
## Replacement for State Hub routes
|
||||
|
||||
| State Hub route | Replacement |
|
||||
| --- | --- |
|
||||
| `GET /execution/semantics` | `GET /execution/semantics` on activity-core (this contract) |
|
||||
| `POST /execution/launch-requests` (workplan) | Do not send here. Queue the workplan in the **repo file**; use an ActivityDefinition + ops_run if the work is a recurring/ops fire |
|
||||
| `GET /execution/launch-requests` | `GET /ops-runs` |
|
||||
| `GET /execution/workplan-stack` | repo-manager work index / hub-core projection — not this API |
|
||||
| `PATCH /execution/workplans/{id}/intent` | Edit the workplan file; repo-manager indexes it |
|
||||
|
||||
Callers that still hit `/execution/*` on activity-core for workplan ids get
|
||||
**410** with `replacement_ref` pointing at the table above.
|
||||
|
||||
## Semantics payload
|
||||
|
||||
`GET /execution/semantics` returns:
|
||||
|
||||
- `activity_core_owns` — schedules, triggers, ops_run claim/complete
|
||||
- `activity_core_does_not_own` — workplan files, task lifecycle, C-rules
|
||||
- `replacements` — map from retired State Hub paths to current surfaces
|
||||
- `port` — `port.schedule` (consume; do not invent the hub-core side here)
|
||||
|
|
@ -778,6 +778,10 @@ retry transient 502/503/504 then **degrade** by default so required workflows do
|
|||
not thrash Temporal when the edge is briefly unavailable. Details:
|
||||
`docs/edge-relay-resilience.md` (ACTIVITY-WP-0027-T06).
|
||||
|
||||
The consistency sweep schedule stays in activity-core; the C-rule engine is
|
||||
repo-manager. Default POST still uses the State Hub dual-run adapter. Override
|
||||
with `CONSISTENCY_SWEEP_URL` or `REPO_MANAGER_URL` (`docs/state-hub-caller-map.md`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Worker fails to start: "ACTCORE_DB_URL is required"
|
||||
|
|
|
|||
63
docs/state-hub-caller-map.md
Normal file
63
docs/state-hub-caller-map.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
id: ACTIVITY-WP-0029-T01
|
||||
type: inventory
|
||||
title: State Hub and hub-core callers
|
||||
updated: "2026-08-18"
|
||||
---
|
||||
|
||||
# State Hub / hub-core caller map
|
||||
|
||||
Inventory for ACTIVITY-WP-0029-T01. Current URLs still go to State Hub unless
|
||||
noted. **Target owner** is who should own the *capability* after retirement.
|
||||
activity-core stays the scheduler even when it is not the engine.
|
||||
|
||||
Hub-core port names are from `helixforge.hub-extension` 0.1
|
||||
(`prj-state-hub-retirement/architecture/hub-extension-contract_v0.1.yaml`).
|
||||
HUB-WP-0004 has not shipped those ports yet.
|
||||
|
||||
| Caller | Current URL | SHR-INV | Target owner | Port (when named) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `StateHubContextResolver.domain_summary` | `GET /state/domain/{domain}` | progress / state-projections | hub-core | `port.projection.query` |
|
||||
| `state_summary` | `GET /state/summary` | state-projections | hub-core | `port.projection.query` |
|
||||
| `next_steps` | `GET /state/next_steps` | state-projections | hub-core | `port.projection.query` |
|
||||
| `workplan_index` | `GET /workplans/index` | work-records | repo-manager | `port.work` |
|
||||
| `hub_inbox` | `GET /messages/` | messaging | hub-core | `port.messaging` |
|
||||
| `pending_decisions` | `GET /decisions/` | work-records | repo-manager | `port.work` |
|
||||
| `coding_retro` | `GET /progress/?event_type=coding_retro` | progress-telemetry | hub-core | `port.events.progress` |
|
||||
| `daily_triage_digest` | `GET /progress/` + workplan/task reads | progress-telemetry | hub-core | `port.events.progress` |
|
||||
| `recently_on_scope_hourly` | `POST /recently-on-scope/hourly` | state-projections | hub-core (generation stays on hub host) | `port.projection.query` |
|
||||
| `consistency_sweep_remote_all` | `POST /consistency/sweep/remote-all` | `job:consistency-sweep-remote-all` + `route:POST:/consistency/sweep/remote-all` | **repo-manager** engine; activity-core schedule | none (RM command, not a hub port) |
|
||||
| `phase5_stabilization_check` | `GET /state/health`, `/state/summary`, `/progress/` | state-projections | hub-core | `port.projection.query` |
|
||||
| `legacy_meter_weekly_review` | `GET /legacy-meter/weekly-review` | catalogs | the-custodian / hub-core catalog | `port.projection.query` |
|
||||
| `binky_rhythm_status` / `fi_brief_status` | `GET /progress/` | progress-telemetry | hub-core | `port.events.progress` |
|
||||
| `repo_sbom_status` | `GET /sbom/{slug}` or `GET /repos/` | sbom-inventory | sbom-nexus (parked: ACTIVITY-WP-0030) | n/a |
|
||||
| `todo_md_staleness` | `GET /repos/todo-md-staleness` | repo-consistency | repo-manager | `port.repo` |
|
||||
| `kaizen` / `reuse_surface` repo list | `GET /repos/` | repo-registry | repo-manager | `port.repo` |
|
||||
| `StateHubProgressSink` | `POST /progress/` | progress-telemetry | hub-core | `port.events.progress` |
|
||||
| `report_sinks` state-hub-progress | `POST /progress/` | progress-telemetry | hub-core | `port.events.progress` |
|
||||
| `ops_evidence_sinks` | `POST /progress/` | progress-telemetry | hub-core | `port.events.interaction` |
|
||||
| `schedule_health` miss reports | `POST /progress/` | progress-telemetry | hub-core | `port.events.progress` |
|
||||
| `ops_console` audit notes | `POST /progress/` | progress-telemetry | hub-core | `port.events.progress` |
|
||||
| `automation_status` / `review_cli` | `GET /progress/` | progress-telemetry | hub-core | `port.events.progress` |
|
||||
| `CoreHubContextResolver.stabilization_check` | `https://hub.coulomb.social` `/healthz` `/readyz` `/api/v2/*` | CORE-WP-0010 | hub-core runtime (core-hub archived after cutover) | compatibility surface |
|
||||
| Manual trigger | `POST /activity-definitions/{id}/trigger` | — | **activity-core** | consumed via `port.schedule` |
|
||||
| Ops-run claim queue | `/ops-runs/*` | workplan-execution-queue (replace) | **activity-core** | consumed via `port.schedule` |
|
||||
|
||||
## Sweep split (T02)
|
||||
|
||||
- **Schedule:** activity-core cron `*/15 * * * *` UTC (`state-hub-consistency-sweep`).
|
||||
- **Engine:** repo-manager C-rules / `rmgr reconcile`.
|
||||
- **Adapter until RM HTTP exists:** State Hub `POST /consistency/sweep/remote-all`
|
||||
(dual-run when `RM_RECONCILE` is on). Override with `CONSISTENCY_SWEEP_URL`
|
||||
or `REPO_MANAGER_URL` + optional `CONSISTENCY_SWEEP_PATH`.
|
||||
|
||||
## Evidence sink (T03 — wait)
|
||||
|
||||
All `POST /progress/` callers stay on the State Hub beachhead until
|
||||
**HUB-WP-0004** publishes `port.events.progress` / `port.events.interaction`.
|
||||
Do not add a second permanent progress host.
|
||||
|
||||
## Execution (T04)
|
||||
|
||||
State Hub `/execution/*` is **not** a task database and must not move here as
|
||||
workplan rows. See `docs/execution-queue-boundary.md`.
|
||||
|
|
@ -243,6 +243,8 @@ data:
|
|||
|
||||
Kubernetes projection of the Custodian-owned definition in
|
||||
`/home/worsch/the-custodian/activity-definitions/state-hub-consistency-sweep.md`.
|
||||
ACTIVITY-WP-0029: activity-core schedules; repo-manager owns the engine;
|
||||
State Hub remains the default dual-run adapter until REPO_MANAGER_URL is set.
|
||||
daily-todo-md-stale-review.md: |
|
||||
---
|
||||
id: "b8e4f1a2-3c6d-4e9f-a1b2-7d8e9f0a1b2c"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ Endpoints:
|
|||
DELETE /activity-definitions/{id} — delete
|
||||
POST /activity-definitions/{id}/trigger — manual one-shot run
|
||||
GET/POST /ops/... — operator console (inventory, status, control)
|
||||
GET /execution/semantics — launch/ops_run contract (ACTIVITY-WP-0029)
|
||||
|
||||
Schedule lifecycle:
|
||||
- POST/PUT with trigger_type='cron' upserts a Temporal Schedule.
|
||||
|
|
@ -40,6 +41,7 @@ from temporalio.api.workflowservice.v1 import GetSystemInfoRequest
|
|||
from temporalio.client import Client
|
||||
|
||||
from activity_core.models import ActivityDefinition, CronTriggerConfig
|
||||
from activity_core.execution_api import router as execution_router
|
||||
from activity_core.ops_api import bind_ops_deps, router as ops_router
|
||||
from activity_core.ops_runs_api import bind_ops_runs_deps, router as ops_runs_router
|
||||
from activity_core.orm import ActivityDefinition as ActivityDefinitionRow, EventType as EventTypeRow
|
||||
|
|
@ -85,6 +87,7 @@ app = FastAPI(title="activity-core API", lifespan=lifespan)
|
|||
app.include_router(webhook_router)
|
||||
app.include_router(ops_router)
|
||||
app.include_router(ops_runs_router)
|
||||
app.include_router(execution_router)
|
||||
|
||||
|
||||
def _get_db() -> async_sessionmaker[AsyncSession]:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ Supported queries:
|
|||
- coding_retro: latest /progress/ item with event_type=coding_retro
|
||||
- daily_triage_digest: curated scalar JSON digest for daily WSJF triage
|
||||
- recently_on_scope_hourly: POST {STATE_HUB_URL}/recently-on-scope/hourly
|
||||
- consistency_sweep_remote_all: POST {STATE_HUB_URL}/consistency/sweep/remote-all
|
||||
- consistency_sweep_remote_all: POST {sweep_base}/consistency/sweep/remote-all
|
||||
sweep_base = CONSISTENCY_SWEEP_URL or REPO_MANAGER_URL or STATE_HUB_URL
|
||||
(ACTIVITY-WP-0029: engine is repo-manager; State Hub is the dual-run
|
||||
adapter until REPO_MANAGER_URL is set)
|
||||
- phase5_stabilization_check: hub-visible Phase 5 stabilization gates
|
||||
- legacy_meter_weekly_review: GET {STATE_HUB_URL}/legacy-meter/weekly-review
|
||||
- binky_rhythm_status: due-items for the Binky operating-rhythm definitions,
|
||||
|
|
@ -68,6 +71,33 @@ def _base_url() -> str:
|
|||
return os.environ.get("STATE_HUB_URL", _DEFAULT_STATE_HUB_URL).rstrip("/")
|
||||
|
||||
|
||||
def _sweep_target() -> tuple[str, str, str]:
|
||||
"""Return (base_url, path, adapter) for the consistency sweep POST.
|
||||
|
||||
Engine owner is always repo-manager. Default adapter is State Hub's
|
||||
dual-run ``POST /consistency/sweep/remote-all``. Point
|
||||
``CONSISTENCY_SWEEP_URL`` or ``REPO_MANAGER_URL`` at repo-manager when
|
||||
that HTTP surface exists.
|
||||
"""
|
||||
path = (os.environ.get("CONSISTENCY_SWEEP_PATH") or "/consistency/sweep/remote-all").strip()
|
||||
if not path.startswith("/"):
|
||||
path = f"/{path}"
|
||||
explicit = (os.environ.get("CONSISTENCY_SWEEP_URL") or "").strip().rstrip("/")
|
||||
repo_mgr = (os.environ.get("REPO_MANAGER_URL") or "").strip().rstrip("/")
|
||||
state_hub = _base_url()
|
||||
if explicit:
|
||||
if repo_mgr and explicit == repo_mgr:
|
||||
adapter = "repo-manager"
|
||||
elif explicit == state_hub:
|
||||
adapter = "state-hub-dual-run"
|
||||
else:
|
||||
adapter = "explicit"
|
||||
return explicit, path, adapter
|
||||
if repo_mgr:
|
||||
return repo_mgr, path, "repo-manager"
|
||||
return state_hub, path, "state-hub-dual-run"
|
||||
|
||||
|
||||
def _fetch_json(path: str, params: dict[str, Any] | None = None) -> Any:
|
||||
url = f"{_base_url()}{path}"
|
||||
try:
|
||||
|
|
@ -104,9 +134,10 @@ def _post_json(
|
|||
*,
|
||||
timeout: float = _TIMEOUT_SECONDS,
|
||||
retries: int | None = None,
|
||||
base_url: str | None = None,
|
||||
) -> Any:
|
||||
"""POST JSON with retries on transient edge/hub failures (ACTIVITY-WP-0027)."""
|
||||
url = f"{_base_url()}{path}"
|
||||
url = f"{(base_url or _base_url()).rstrip('/')}{path}"
|
||||
attempts = _post_retry_attempts() if retries is None else max(1, retries)
|
||||
backoff = _post_retry_backoff_seconds()
|
||||
last_exc: Exception | None = None
|
||||
|
|
@ -261,11 +292,13 @@ class StateHubContextResolver(ContextResolver):
|
|||
for key, value in params.items()
|
||||
if key not in {"required", "degrade_on_unavailable"}
|
||||
}
|
||||
sweep_base, sweep_path, adapter = _sweep_target()
|
||||
try:
|
||||
result = _post_json(
|
||||
"/consistency/sweep/remote-all",
|
||||
sweep_path,
|
||||
payload,
|
||||
timeout=_SWEEP_TIMEOUT_SECONDS,
|
||||
base_url=sweep_base,
|
||||
)
|
||||
except (httpx.HTTPError, ValueError) as exc:
|
||||
if not _want_degrade(params):
|
||||
|
|
@ -283,8 +316,15 @@ class StateHubContextResolver(ContextResolver):
|
|||
"skipped_budget": [],
|
||||
"degraded": True,
|
||||
"degraded_reason": str(exc)[:300],
|
||||
"engine_owner": "repo-manager",
|
||||
"adapter": adapter,
|
||||
"sweep_url": f"{sweep_base}{sweep_path}",
|
||||
}
|
||||
return _validate_consistency_sweep_remote_all(result)
|
||||
validated = _validate_consistency_sweep_remote_all(result)
|
||||
validated.setdefault("engine_owner", "repo-manager")
|
||||
validated.setdefault("adapter", adapter)
|
||||
validated.setdefault("sweep_url", f"{sweep_base}{sweep_path}")
|
||||
return validated
|
||||
if query == "phase5_stabilization_check":
|
||||
return _phase5_stabilization_check(params)
|
||||
if query == "legacy_meter_weekly_review":
|
||||
|
|
|
|||
88
src/activity_core/execution_api.py
Normal file
88
src/activity_core/execution_api.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
"""Execution-queue contract surface (ACTIVITY-WP-0029-T04).
|
||||
|
||||
Workplan launch rows stay out of this API. Callers that send State Hub
|
||||
``/execution/*`` workplan shapes get 410 with a replacement pointer.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
router = APIRouter(prefix="/execution", tags=["execution"])
|
||||
|
||||
SEMANTICS = {
|
||||
"port": "port.schedule",
|
||||
"document": "docs/execution-queue-boundary.md",
|
||||
"activity_core_owns": [
|
||||
"schedules, wakeups, and recurring ActivityDefinition fires",
|
||||
"POST /activity-definitions/{id}/trigger",
|
||||
"ops_run claim, heartbeat, complete, and fail",
|
||||
],
|
||||
"activity_core_does_not_own": [
|
||||
"workplan and task files (ADR-001 / repo-manager index)",
|
||||
"task lifecycle assign/track/close (issue-core / work records)",
|
||||
"ITC Task Model types",
|
||||
"policy publication (policy-nexus)",
|
||||
"C-rule consistency engine (repo-manager)",
|
||||
],
|
||||
"replacements": {
|
||||
"GET /execution/semantics": "GET /execution/semantics",
|
||||
"GET /execution/launch-requests": "GET /ops-runs",
|
||||
"POST /execution/launch-requests": (
|
||||
"POST /activity-definitions/{id}/trigger for a schedule fire; "
|
||||
"do not POST workplan ids here"
|
||||
),
|
||||
"GET /execution/workplan-stack": "repo-manager port.work / hub-core projection",
|
||||
"PATCH /execution/workplans/{id}/intent": "edit the workplan file in its repo",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/semantics")
|
||||
async def execution_semantics() -> dict:
|
||||
return SEMANTICS
|
||||
|
||||
|
||||
def _gone(replacement: str, detail: str) -> JSONResponse:
|
||||
return JSONResponse(
|
||||
status_code=410,
|
||||
content={
|
||||
"detail": detail,
|
||||
"replacement_ref": replacement,
|
||||
"document": "docs/execution-queue-boundary.md",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.api_route("/launch-requests", methods=["GET", "POST"])
|
||||
async def launch_requests_retired() -> JSONResponse:
|
||||
return _gone(
|
||||
SEMANTICS["replacements"]["GET /execution/launch-requests"],
|
||||
"Workplan launch-requests are not stored in activity-core. "
|
||||
"Use GET /ops-runs or POST /activity-definitions/{id}/trigger.",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/workplan-stack")
|
||||
async def workplan_stack_retired() -> JSONResponse:
|
||||
return _gone(
|
||||
SEMANTICS["replacements"]["GET /execution/workplan-stack"],
|
||||
"Workplan stacks are a work-record index, not an ops-run queue.",
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/workplans/{workplan_id}/intent")
|
||||
async def workplan_intent_retired(workplan_id: str) -> JSONResponse:
|
||||
return _gone(
|
||||
SEMANTICS["replacements"]["PATCH /execution/workplans/{id}/intent"],
|
||||
"Execution intent for a workplan belongs in the repo file, not here.",
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/workstreams/{workstream_id}/intent")
|
||||
async def workstream_intent_retired(workstream_id: str) -> JSONResponse:
|
||||
return _gone(
|
||||
SEMANTICS["replacements"]["PATCH /execution/workplans/{id}/intent"],
|
||||
"Legacy workstream execution intent is retired with State Hub.",
|
||||
)
|
||||
39
tests/test_execution_api.py
Normal file
39
tests/test_execution_api.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from activity_core.execution_api import router
|
||||
|
||||
|
||||
def _client() -> TestClient:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def test_execution_semantics_names_boundary() -> None:
|
||||
client = _client()
|
||||
response = client.get("/execution/semantics")
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["port"] == "port.schedule"
|
||||
assert any("ops_run" in item for item in body["activity_core_owns"])
|
||||
assert any("workplan" in item.lower() for item in body["activity_core_does_not_own"])
|
||||
assert body["replacements"]["GET /execution/launch-requests"] == "GET /ops-runs"
|
||||
|
||||
|
||||
def test_execution_workplan_routes_are_gone() -> None:
|
||||
client = _client()
|
||||
gone = [
|
||||
client.get("/execution/launch-requests"),
|
||||
client.post("/execution/launch-requests", json={"workplan_id": "x"}),
|
||||
client.get("/execution/workplan-stack"),
|
||||
client.patch("/execution/workplans/abc/intent", json={}),
|
||||
client.patch("/execution/workstreams/abc/intent", json={}),
|
||||
]
|
||||
for response in gone:
|
||||
assert response.status_code == 410
|
||||
payload = response.json()
|
||||
assert "replacement_ref" in payload
|
||||
assert "execution-queue-boundary" in payload["document"]
|
||||
|
|
@ -553,6 +553,9 @@ def test_consistency_sweep_remote_all_posts_batch(monkeypatch) -> None:
|
|||
)
|
||||
|
||||
monkeypatch.setenv("STATE_HUB_URL", "http://state-hub.test/")
|
||||
monkeypatch.delenv("REPO_MANAGER_URL", raising=False)
|
||||
monkeypatch.delenv("CONSISTENCY_SWEEP_URL", raising=False)
|
||||
monkeypatch.delenv("CONSISTENCY_SWEEP_PATH", raising=False)
|
||||
monkeypatch.setattr(httpx, "post", fake_post)
|
||||
|
||||
result = StateHubContextResolver().resolve(
|
||||
|
|
@ -563,6 +566,9 @@ def test_consistency_sweep_remote_all_posts_batch(monkeypatch) -> None:
|
|||
|
||||
assert result["exit_code"] == 0
|
||||
assert result["repos_processed"][0]["repo_slug"] == "state-hub"
|
||||
assert result["engine_owner"] == "repo-manager"
|
||||
assert result["adapter"] == "state-hub-dual-run"
|
||||
assert result["sweep_url"] == "http://state-hub.test/consistency/sweep/remote-all"
|
||||
assert calls == [
|
||||
{
|
||||
"url": "http://state-hub.test/consistency/sweep/remote-all",
|
||||
|
|
@ -572,6 +578,37 @@ def test_consistency_sweep_remote_all_posts_batch(monkeypatch) -> None:
|
|||
]
|
||||
|
||||
|
||||
def test_consistency_sweep_uses_repo_manager_url(monkeypatch) -> None:
|
||||
calls: list[dict[str, Any]] = []
|
||||
|
||||
def fake_post(url: str, **kwargs: Any) -> DummyResponse:
|
||||
calls.append({"url": url, **kwargs})
|
||||
return DummyResponse(
|
||||
{
|
||||
"exit_code": 0,
|
||||
"lock_skipped": False,
|
||||
"repos_processed": [],
|
||||
}
|
||||
)
|
||||
|
||||
monkeypatch.setenv("STATE_HUB_URL", "http://state-hub.test/")
|
||||
monkeypatch.setenv("REPO_MANAGER_URL", "http://repo-manager.test")
|
||||
monkeypatch.setenv("CONSISTENCY_SWEEP_PATH", "/v1/consistency/sweep/remote-all")
|
||||
monkeypatch.setattr(httpx, "post", fake_post)
|
||||
|
||||
result = StateHubContextResolver().resolve(
|
||||
"consistency_sweep_remote_all",
|
||||
None,
|
||||
{"max_seconds": 300},
|
||||
)
|
||||
|
||||
assert result["adapter"] == "repo-manager"
|
||||
assert result["engine_owner"] == "repo-manager"
|
||||
assert calls[0]["url"] == (
|
||||
"http://repo-manager.test/v1/consistency/sweep/remote-all"
|
||||
)
|
||||
|
||||
|
||||
def test_consistency_sweep_remote_all_failure_bubbles(monkeypatch) -> None:
|
||||
def fake_post(url: str, **kwargs: Any) -> DummyResponse:
|
||||
raise httpx.ConnectError("offline")
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ Out of this workplan: SBOM catch-up (CUST-WP-0062 / ACTIVITY-WP-0030, parked).
|
|||
|
||||
```task
|
||||
id: ACTIVITY-WP-0029-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "03021be2-5d60-455b-9c9c-b8681d65911c"
|
||||
```
|
||||
|
|
@ -93,7 +93,7 @@ Do not retarget a caller to activity-core just because we schedule it.
|
|||
|
||||
```task
|
||||
id: ACTIVITY-WP-0029-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "f8107e2b-ab1f-4a3b-9348-8d5dcfb647b4"
|
||||
```
|
||||
|
|
@ -122,7 +122,7 @@ smokes when the port exists. Until then, keep the State Hub beachhead
|
|||
|
||||
```task
|
||||
id: ACTIVITY-WP-0029-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "a34b42c3-8a08-482b-936f-14675ee254ca"
|
||||
```
|
||||
|
|
@ -134,7 +134,10 @@ index, issue-core lifecycle, ITC Task Model types, policy-nexus publication.
|
|||
|
||||
## Acceptance
|
||||
|
||||
- [ ] Caller map lists owner + current URL for every State Hub / core-hub call
|
||||
- [ ] Sweep still fires from activity-core and hits repo-manager (or dual-run)
|
||||
- [ ] Evidence sink plan names hub-core as the permanent host; T03 stays wait until that port exists
|
||||
- [ ] Execution boundary is written so `/execution/*` cannot be read as "activity-core owns tasks"
|
||||
- [x] Caller map lists owner + current URL for every State Hub / core-hub call
|
||||
(`docs/state-hub-caller-map.md`)
|
||||
- [x] Sweep still fires from activity-core and hits repo-manager (or dual-run)
|
||||
(`CONSISTENCY_SWEEP_URL` / `REPO_MANAGER_URL`; default State Hub adapter)
|
||||
- [x] Evidence sink plan names hub-core as the permanent host; T03 stays wait until that port exists
|
||||
- [x] Execution boundary is written so `/execution/*` cannot be read as "activity-core owns tasks"
|
||||
(`docs/execution-queue-boundary.md`, `GET /execution/semantics`, 410 on workplan routes)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue