generated from coulomb/repo-seed
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:
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user