feat(capability-requests): add routing_note, PATCH endpoint, word-boundary fix, and ops-bridge tunnel targets

- Add `routing_note` column (migration l9g0h1i2j3k4) to persist why a request was routed to a given domain
- Fix substring-match bug in `_route_capability`: use `\b` word-boundary regex so 'postgres' no longer matches inside 'postgresql'
- Include `title` in keyword scoring for better routing accuracy
- Return `routing_note` string from `_route_capability` and store it on the request
- Add `PATCH /capability-requests/{id}` endpoint + `CapabilityRequestPatch` schema to correct mutable metadata (catalog_entry_id, priority, blocking_task_id, fulfilling_workstream_id)
- Add `patch_capability_request` MCP tool wrapping the new endpoint
- Add 105 lines of routing tests (word-boundary, title-match, multi-entry scoring, broadcast fallback)
- Add `tunnels-up`, `tunnels-status`, `tunnels-check` Makefile targets for ops-bridge managed tunnels

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 03:47:54 +01:00
parent 101c953e69
commit 62d407cae7
7 changed files with 282 additions and 18 deletions

View File

@@ -335,3 +335,108 @@ class TestCapabilityRequestLifecycle:
"status": "requested",
})
assert r.status_code == 422
class TestCapabilityRequestRouting:
async def test_word_boundary_avoids_substring_false_positive(self, client):
"""'postgres' keyword must not match inside 'postgresql'."""
await _setup_two_domains(client)
# Register two entries: PostgreSQL HA (keywords: postgres, ha) and k3s (keywords: k3s, cluster)
await _register_catalog(client, domain="railiance", cap_type="infrastructure",
title="PostgreSQL HA", keywords=["postgresql", "postgres", "ha"])
await _register_catalog(client, domain="custodian", cap_type="infrastructure",
title="K3s provisioning", keywords=["k3s", "cluster", "k8s"])
# Description mentions k8s and cluster but NOT standalone "postgres" or "ha"
req = await _create_request(
client,
title="K3s cluster access",
description="Need k8s foundations. cluster must be up. kubeconfig required.",
cap_type="infrastructure",
)
# k3s entry should win (k8s + cluster match), not postgres entry
assert req["catalog_entry_id"] is not None
# Routing note should mention k3s
assert req["routing_note"] is not None
assert "K3s" in req["routing_note"] or "k3s" in req["routing_note"].lower()
async def test_title_included_in_routing(self, client):
"""Title keywords should contribute to routing score."""
await _setup_two_domains(client)
await _register_catalog(client, domain="railiance", cap_type="infrastructure",
title="K3s cluster", keywords=["k3s", "cluster", "kubernetes"])
await _register_catalog(client, domain="custodian", cap_type="infrastructure",
title="Postgres DB", keywords=["postgresql", "postgres", "database"])
# Title contains "k3s" but description is generic
req = await _create_request(
client,
title="k3s cluster access needed",
description="Need access to proceed with deployment.",
cap_type="infrastructure",
)
assert req["routing_note"] is not None
assert req["catalog_entry_id"] is not None
# Should match k3s (title has "k3s" and "cluster")
# Verify via routing note
assert "K3s" in req["routing_note"] or "k3s" in req["routing_note"].lower()
async def test_ambiguous_routing_broadcasts(self, client):
"""Tied scores should broadcast (no fulfilling domain)."""
await _setup_two_domains(client)
await _register_catalog(client, domain="railiance", cap_type="infrastructure",
title="Entry A", keywords=["cluster"])
await _register_catalog(client, domain="custodian", cap_type="infrastructure",
title="Entry B", keywords=["cluster"])
req = await _create_request(
client,
title="cluster needed",
description="cluster access",
cap_type="infrastructure",
)
assert req["fulfilling_domain_slug"] is None
assert "ambiguous" in req["routing_note"]
async def test_patch_corrects_catalog_entry_and_reroutes(self, client):
"""PATCH /capability-requests/{id} corrects catalog_entry_id and re-derives domain."""
req_d, ful_d = await _setup_two_domains(client)
# Register wrong entry (postgres) and correct entry (k3s)
wrong = await _register_catalog(client, domain="railiance", cap_type="infrastructure",
title="PostgreSQL HA", keywords=["postgresql"])
correct = await _register_catalog(client, domain="custodian", cap_type="infrastructure",
title="K3s cluster", keywords=["k3s"])
# Create request — auto-routes to postgres (only postgres keyword matches "postgresql" in description)
req = await _create_request(
client,
description="Need postgresql and k3s access",
cap_type="infrastructure",
)
# Patch to correct catalog entry
r = await client.patch(f"/capability-requests/{req['id']}", json={
"catalog_entry_id": correct["id"],
})
assert r.status_code == 200
data = r.json()
assert data["catalog_entry_id"] == correct["id"]
assert data["fulfilling_domain_slug"] == "custodian"
assert "hub correction" in data["routing_note"]
assert "K3s" in data["routing_note"]
async def test_patch_unknown_catalog_entry_404(self, client):
await _setup_two_domains(client)
req = await _create_request(client, cap_type="security")
import uuid as _uuid
r = await client.patch(f"/capability-requests/{req['id']}", json={
"catalog_entry_id": str(_uuid.uuid4()),
})
assert r.status_code == 404
async def test_patch_priority(self, client):
await _setup_two_domains(client)
req = await _create_request(client, cap_type="security")
r = await client.patch(f"/capability-requests/{req['id']}", json={"priority": "critical"})
assert r.status_code == 200
assert r.json()["priority"] == "critical"
assert "priority" in r.json()["routing_note"]