feat: make repository reads alias-aware
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
parent
639b9aed08
commit
2e2ae1e5d0
19 changed files with 982 additions and 94 deletions
292
tests/test_repository_alias_routing.py
Normal file
292
tests/test_repository_alias_routing.py
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
|
||||
from api.models.agent_message import AgentMessage
|
||||
from api.models.fabric_graph import FabricGraphImport, FabricGraphNode
|
||||
from api.models.interface_change import InterfaceChange
|
||||
from api.models.managed_repo import ManagedRepo
|
||||
from api.models.repository_rename import RepositorySlug
|
||||
from tests.conftest import (
|
||||
create_test_domain,
|
||||
create_test_repo,
|
||||
create_test_workplan,
|
||||
)
|
||||
|
||||
|
||||
async def _rename_in_state_hub(test_engine, repo_id: str) -> None:
|
||||
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
|
||||
async with factory() as session:
|
||||
repo = await session.get(ManagedRepo, repo_id)
|
||||
old = (
|
||||
await session.execute(
|
||||
select(RepositorySlug).where(RepositorySlug.slug == "flex-auth")
|
||||
)
|
||||
).scalar_one()
|
||||
old.kind = "alias"
|
||||
old.protected = True
|
||||
repo.slug = "access-engine"
|
||||
repo.name = "Access Engine"
|
||||
session.add(
|
||||
RepositorySlug(
|
||||
repo_id=repo.id,
|
||||
slug="access-engine",
|
||||
kind="canonical",
|
||||
protected=True,
|
||||
)
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_alias_lookup_dispatch_bindings_and_external_staleness(
|
||||
client, test_engine
|
||||
):
|
||||
domain = await create_test_domain(client)
|
||||
repo = await create_test_repo(client, domain_slug=domain["slug"], slug="flex-auth")
|
||||
workplan = await create_test_workplan(
|
||||
client,
|
||||
repo_id=repo["id"],
|
||||
slug="FLEX-WP-0001",
|
||||
title="Security stack migration",
|
||||
)
|
||||
task = await client.post(
|
||||
"/tasks/",
|
||||
json={
|
||||
"workplan_id": workplan["id"],
|
||||
"title": "Preserve identity",
|
||||
"status": "todo",
|
||||
"priority": "high",
|
||||
},
|
||||
)
|
||||
assert task.status_code == 201, task.text
|
||||
|
||||
await _rename_in_state_hub(test_engine, repo["id"])
|
||||
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
|
||||
async with factory() as session:
|
||||
change = InterfaceChange(
|
||||
repo_id=repo["id"],
|
||||
interface_type="rest_api",
|
||||
change_type="breaking",
|
||||
title="Historical consumer notice",
|
||||
description="Recorded before cutover",
|
||||
affected_paths=["/authorize"],
|
||||
affected_repo_slugs=["flex-auth"],
|
||||
status="published",
|
||||
published_at=datetime.now(timezone.utc),
|
||||
author="pytest",
|
||||
)
|
||||
import_run = FabricGraphImport(
|
||||
source_repo_slug="railiance-fabric",
|
||||
content_hash="a" * 64,
|
||||
graph_json={},
|
||||
validation_status="valid",
|
||||
is_latest=True,
|
||||
)
|
||||
session.add_all([change, import_run])
|
||||
await session.flush()
|
||||
session.add(
|
||||
FabricGraphNode(
|
||||
import_id=import_run.id,
|
||||
source_repo_slug="railiance-fabric",
|
||||
graph_id="repo:flex-auth",
|
||||
kind="repository",
|
||||
name="Flex Auth",
|
||||
repo_slug="flex-auth",
|
||||
domain_slug="infotech",
|
||||
lifecycle="active",
|
||||
)
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
old_lookup = await client.get("/repos/flex-auth")
|
||||
new_lookup = await client.get("/repos/access-engine")
|
||||
assert old_lookup.status_code == new_lookup.status_code == 200
|
||||
assert old_lookup.json()["id"] == new_lookup.json()["id"] == repo["id"]
|
||||
assert old_lookup.json()["requested_slug"] == "flex-auth"
|
||||
assert old_lookup.json()["canonical_slug"] == "access-engine"
|
||||
assert old_lookup.json()["slug_status"] == "alias"
|
||||
assert old_lookup.json()["aliases"] == ["flex-auth"]
|
||||
assert new_lookup.json()["slug_status"] == "canonical"
|
||||
assert old_lookup.json()["stale_external_references"] == [
|
||||
{
|
||||
"owner": "railiance-fabric",
|
||||
"surface": "fabric_graph_nodes",
|
||||
"field": "repo_slug",
|
||||
"value": "flex-auth",
|
||||
"count": 1,
|
||||
"status": "stale",
|
||||
"handoff": "owner update or re-ingest required",
|
||||
}
|
||||
]
|
||||
|
||||
dispatch = await client.get("/repos/access-engine/dispatch")
|
||||
assert dispatch.status_code == 200, dispatch.text
|
||||
assert dispatch.json()["repo_slug"] == "access-engine"
|
||||
assert dispatch.json()["pending_interface_changes"][0]["title"] == change.title
|
||||
|
||||
bound = await client.get(f"/workplans/{workplan['id']}")
|
||||
assert bound.status_code == 200
|
||||
assert bound.json()["id"] == workplan["id"]
|
||||
assert bound.json()["repo_id"] == repo["id"]
|
||||
bound_task = await client.get(f"/tasks/{task.json()['id']}")
|
||||
assert bound_task.status_code == 200
|
||||
assert bound_task.json()["id"] == task.json()["id"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_history_is_immutable_and_old_slug_write_replays_once(
|
||||
client, test_engine
|
||||
):
|
||||
domain = await create_test_domain(client)
|
||||
repo = await create_test_repo(client, domain_slug=domain["slug"], slug="flex-auth")
|
||||
await _rename_in_state_hub(test_engine, repo["id"])
|
||||
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
|
||||
async with factory() as session:
|
||||
historical = AgentMessage(
|
||||
from_agent="security-review",
|
||||
to_agent="flex-auth",
|
||||
subject="Before cutover",
|
||||
body="Historical recipient must not be rewritten",
|
||||
)
|
||||
session.add(historical)
|
||||
await session.commit()
|
||||
historical_id = historical.id
|
||||
|
||||
history = await client.get("/messages/", params={"to_agent": "access-engine"})
|
||||
assert history.status_code == 200
|
||||
assert [item["id"] for item in history.json()] == [str(historical_id)]
|
||||
assert history.json()[0]["to_agent"] == "flex-auth"
|
||||
|
||||
payload = {
|
||||
"from_agent": "flex-auth",
|
||||
"to_agent": "flex-auth",
|
||||
"subject": "Queued during cutover",
|
||||
"body": "Replay exactly once",
|
||||
}
|
||||
headers = {"Idempotency-Key": "repo-rename-old-slug-message"}
|
||||
first = await client.post("/messages/", json=payload, headers=headers)
|
||||
replay = await client.post("/messages/", json=payload, headers=headers)
|
||||
assert first.status_code == replay.status_code == 201
|
||||
assert first.json()["id"] == replay.json()["id"]
|
||||
assert first.json()["from_agent"] == "access-engine"
|
||||
assert first.json()["to_agent"] == "access-engine"
|
||||
|
||||
async with factory() as session:
|
||||
assert await session.scalar(
|
||||
select(func.count())
|
||||
.select_from(AgentMessage)
|
||||
.where(AgentMessage.subject == "Queued during cutover")
|
||||
) == 1
|
||||
unchanged = await session.get(AgentMessage, historical_id)
|
||||
assert unchanged.to_agent == "flex-auth"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_alias_aware_interface_catalog_sbom_and_telemetry_reads(
|
||||
client, test_engine
|
||||
):
|
||||
domain = await create_test_domain(client)
|
||||
repo = await create_test_repo(client, domain_slug=domain["slug"], slug="flex-auth")
|
||||
await _rename_in_state_hub(test_engine, repo["id"])
|
||||
|
||||
interface = await client.post(
|
||||
"/interface-changes/",
|
||||
json={
|
||||
"repo_slug": "flex-auth",
|
||||
"interface_type": "rest_api",
|
||||
"change_type": "additive",
|
||||
"title": "Canonical write",
|
||||
"description": "New references use the current slug",
|
||||
"affected_repo_slugs": ["flex-auth"],
|
||||
},
|
||||
)
|
||||
assert interface.status_code == 201, interface.text
|
||||
assert interface.json()["repo_slug"] == "access-engine"
|
||||
assert interface.json()["affected_repo_slugs"] == ["access-engine"]
|
||||
affected = await client.get(
|
||||
"/interface-changes/", params={"affected_repo": "flex-auth"}
|
||||
)
|
||||
assert [item["id"] for item in affected.json()] == [interface.json()["id"]]
|
||||
|
||||
goal = await client.post(
|
||||
"/repo-goals/",
|
||||
json={
|
||||
"repo_id": repo["id"],
|
||||
"title": "Preserve authorization continuity",
|
||||
"description": "Keep Net Kingdom consumers online",
|
||||
},
|
||||
)
|
||||
assert goal.status_code == 201, goal.text
|
||||
goals = await client.get("/repo-goals/", params={"repo_slug": "flex-auth"})
|
||||
assert [item["id"] for item in goals.json()] == [goal.json()["id"]]
|
||||
assert goals.json()[0]["repo_slug"] == "access-engine"
|
||||
|
||||
capability = await client.post(
|
||||
"/capability-catalog/",
|
||||
json={
|
||||
"domain": "infotech",
|
||||
"repo_slug": "flex-auth",
|
||||
"capability_type": "authorization",
|
||||
"title": "Policy decisions",
|
||||
"description": "Net Kingdom authorization",
|
||||
"keywords": ["authorize"],
|
||||
},
|
||||
)
|
||||
assert capability.status_code == 201, capability.text
|
||||
catalog = await client.get(
|
||||
"/capability-catalog/", params={"repo_slug": "access-engine"}
|
||||
)
|
||||
assert [item["id"] for item in catalog.json()] == [capability.json()["id"]]
|
||||
assert catalog.json()[0]["repo_slug"] == "access-engine"
|
||||
|
||||
service = await client.post(
|
||||
"/services/catalog",
|
||||
json={
|
||||
"slug": "access-engine-api",
|
||||
"name": "Access Engine API",
|
||||
"hosting_type": "self_hosted",
|
||||
"development_type": "first_party",
|
||||
"first_party": {"repo_slug": "flex-auth", "owning_domain": "infotech"},
|
||||
"self_hosted": {},
|
||||
},
|
||||
)
|
||||
assert service.status_code == 201, service.text
|
||||
services = await client.get("/services/catalog", params={"repo_slug": "flex-auth"})
|
||||
assert [item["id"] for item in services.json()] == [service.json()["id"]]
|
||||
|
||||
ingested = await client.post(
|
||||
"/sbom/ingest/",
|
||||
json={
|
||||
"repo_slug": "flex-auth",
|
||||
"entries": [
|
||||
{
|
||||
"package_name": "opa",
|
||||
"package_version": "1.0",
|
||||
"ecosystem": "go",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
assert ingested.status_code == 200, ingested.text
|
||||
assert ingested.json()["repo_slug"] == "access-engine"
|
||||
sbom = await client.get("/sbom/flex-auth")
|
||||
assert sbom.status_code == 200
|
||||
assert sbom.json()["repo_slug"] == "access-engine"
|
||||
|
||||
event = await client.post(
|
||||
"/token-events/",
|
||||
json={"repo_id": repo["id"], "tokens_in": 12, "tokens_out": 3},
|
||||
)
|
||||
assert event.status_code == 201, event.text
|
||||
summary = await client.get(
|
||||
"/token-events/by-repo/", params={"repo_slug": "flex-auth"}
|
||||
)
|
||||
assert summary.status_code == 200
|
||||
assert len(summary.json()) == 1
|
||||
assert summary.json()[0]["repo_slug"] == "access-engine"
|
||||
assert summary.json()[0]["tokens_total"] == 15
|
||||
|
|
@ -12,6 +12,7 @@ from api.models import Base
|
|||
from api.models.domain import Domain
|
||||
from api.models.managed_repo import ManagedRepo
|
||||
from api.models.progress_event import ProgressEvent
|
||||
from api.models.repository_rename import RepositorySlug
|
||||
from api.models.task import Task
|
||||
from api.models.topic import Topic
|
||||
from api.models.token_event import TokenEvent
|
||||
|
|
@ -276,6 +277,57 @@ async def test_repository_migration_cascades_and_reverses(test_engine):
|
|||
assert all(alias.reversed_at is not None for alias in aliases)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_repository_migration_accepts_protected_prior_slug(test_engine):
|
||||
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
|
||||
ids = await _seed_projection(factory, "flex-auth")
|
||||
plan = _sealed_plan("flex-auth", ids["workplan"], ids["task"])
|
||||
|
||||
async with factory() as session:
|
||||
repo = (
|
||||
await session.execute(
|
||||
select(ManagedRepo).where(ManagedRepo.slug == "flex-auth")
|
||||
)
|
||||
).scalar_one()
|
||||
repo.slug = "access-engine"
|
||||
session.add_all(
|
||||
[
|
||||
RepositorySlug(
|
||||
repo_id=repo.id,
|
||||
slug="flex-auth",
|
||||
kind="alias",
|
||||
protected=True,
|
||||
),
|
||||
RepositorySlug(
|
||||
repo_id=repo.id,
|
||||
slug="access-engine",
|
||||
kind="canonical",
|
||||
protected=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
async with factory() as session:
|
||||
result = await apply_repository_identifier_migration(session, plan, "flex-auth")
|
||||
assert result.direction == "forward"
|
||||
|
||||
async with factory() as session:
|
||||
aliases = list((await session.execute(select(WorkRecordIdentifierAlias))).scalars())
|
||||
assert len(aliases) == 2
|
||||
assert {alias.repo_slug for alias in aliases} == {"flex-auth"}
|
||||
repo = (
|
||||
await session.execute(
|
||||
select(ManagedRepo).where(ManagedRepo.slug == "access-engine")
|
||||
)
|
||||
).scalar_one()
|
||||
assert repo.id is not None
|
||||
|
||||
async with factory() as session:
|
||||
result = await reverse_repository_identifier_migration(session, plan, "flex-auth")
|
||||
assert result.direction == "reverse"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_repository_migration_is_atomic_when_a_source_is_missing(test_engine):
|
||||
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue