feat: implement ISSUE-WP-0005 connector mapping and scope alignment
Add MappingService (work_record_uuid ↔ external id), project/map CLI with outward push-status, optional TaskSpec work_record fields, boundary-sync policy, and packaging/capability framing cleanup. Mark WP-0005 finished.
This commit is contained in:
parent
6855fb2a32
commit
f88e9a28fe
16 changed files with 1300 additions and 179 deletions
|
|
@ -21,6 +21,7 @@ from ..backends.gitea import GiteaBackend
|
|||
from ..backends.local import LocalSQLiteBackend
|
||||
from ..cli.utils import get_config_dir, load_backend_configs
|
||||
from ..core.interfaces import BackendFactory, IssueBackend
|
||||
from ..core.mapping import MappingService
|
||||
from ..core.models import Issue, IssueState, Label
|
||||
from .auth import require_api_key
|
||||
from .schemas import BackendName, TaskIngestionRequest, TaskIngestionResponse
|
||||
|
|
@ -87,6 +88,12 @@ def _build_issue(payload: TaskIngestionRequest, backend_type: str) -> Issue:
|
|||
ingestion_meta["due_at"] = (now + timedelta(days=payload.due_in_days)).isoformat()
|
||||
|
||||
sync_metadata: Dict[str, Any] = {"ingestion": ingestion_meta}
|
||||
if payload.work_record_uuid:
|
||||
sync_metadata["mapping"] = {
|
||||
"work_record_uuid": payload.work_record_uuid,
|
||||
"work_record_id": payload.work_record_id,
|
||||
"work_record_kind": payload.work_record_kind,
|
||||
}
|
||||
|
||||
return Issue(
|
||||
id="",
|
||||
|
|
@ -102,15 +109,65 @@ def _build_issue(payload: TaskIngestionRequest, backend_type: str) -> Issue:
|
|||
)
|
||||
|
||||
|
||||
def _mapping_backend_name(backend_type: str) -> str:
|
||||
return "sqlite" if backend_type == "local" else backend_type
|
||||
|
||||
|
||||
def _upsert_mapping_if_requested(
|
||||
payload: TaskIngestionRequest,
|
||||
*,
|
||||
backend_type: str,
|
||||
issue_id: str,
|
||||
issue_url: Optional[str],
|
||||
) -> None:
|
||||
if not payload.work_record_uuid:
|
||||
return
|
||||
svc = MappingService()
|
||||
svc.connect(str(get_config_dir() / "mappings.db"))
|
||||
try:
|
||||
svc.upsert(
|
||||
work_record_uuid=payload.work_record_uuid,
|
||||
work_record_id=payload.work_record_id,
|
||||
work_record_kind=payload.work_record_kind,
|
||||
backend=_mapping_backend_name(backend_type),
|
||||
external_id=issue_id,
|
||||
external_url=issue_url,
|
||||
target_repo=payload.target_repo,
|
||||
direction="outward",
|
||||
)
|
||||
finally:
|
||||
svc.disconnect()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/issues/",
|
||||
response_model=TaskIngestionResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(require_api_key)],
|
||||
summary="Ingest a task from an external emitter (e.g. activity-core).",
|
||||
summary="Create an external tracker issue (intentional emit; optional work-record mapping).",
|
||||
)
|
||||
async def ingest_task(payload: TaskIngestionRequest) -> TaskIngestionResponse:
|
||||
backend, backend_type = _resolve_backend()
|
||||
backend_name: BackendName = _BACKEND_TYPE_TO_NAME.get(backend_type, backend_type) # type: ignore[assignment]
|
||||
mapping_backend = _mapping_backend_name(backend_type)
|
||||
|
||||
# Idempotent: if work_record_uuid already mapped, return existing issue.
|
||||
if payload.work_record_uuid:
|
||||
map_svc = MappingService()
|
||||
map_svc.connect(str(get_config_dir() / "mappings.db"))
|
||||
try:
|
||||
existing = map_svc.get_active_by_uuid(
|
||||
payload.work_record_uuid, backend=mapping_backend
|
||||
)
|
||||
if existing:
|
||||
return TaskIngestionResponse(
|
||||
issue_id=existing.external_id,
|
||||
issue_url=existing.external_url,
|
||||
backend=backend_name,
|
||||
)
|
||||
finally:
|
||||
map_svc.disconnect()
|
||||
|
||||
draft = _build_issue(payload, backend_type)
|
||||
try:
|
||||
created: Issue = backend.create_issue(draft)
|
||||
|
|
@ -131,7 +188,20 @@ async def ingest_task(payload: TaskIngestionRequest) -> TaskIngestionResponse:
|
|||
issue_url: Optional[str] = None
|
||||
if created.sync_metadata:
|
||||
issue_url = created.sync_metadata.get("url") or created.sync_metadata.get("html_url")
|
||||
backend_name: BackendName = _BACKEND_TYPE_TO_NAME.get(backend_type, backend_type) # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
_upsert_mapping_if_requested(
|
||||
payload,
|
||||
backend_type=backend_type,
|
||||
issue_id=issue_id,
|
||||
issue_url=issue_url,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
|
||||
return TaskIngestionResponse(
|
||||
issue_id=issue_id,
|
||||
issue_url=issue_url,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue