Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,7 @@
- HTTP 확장4(ADR-0084, 구현·로컬 검증 완료): run/resume의 연결별 bearer env override를 별도 map으로 받으며 저장된 연결 인증을 바꾸지 않는다. 참조만 manifest/profile에 결합하고 누락·변경·값 부재/invalid에서 fallback 없이 거절한다. shipment/sensor의 다른 토큰·연결 불변·상태/모델 비노출·토큰 회전·완료 replay와 echo 차단을 loopback에서 확인했다. 서버의 사용자 인증·대리 권한·Run 접근 제어·process/state 격리는 외부 호스트 책임이며 실제 통합은 미검증이다.
- HTTP 확장4 검증: Rust784개(4 ignored), pinned backend+컴파일 CLI Python153개(7 skipped), Clippy·fmt·protocol·문서·workflow 검사 통과. 한 Run의 연결2개/서로 다른 토큰도 올바르게 전달됐고 모든 상태 파일·모델 요청에 값이 없었다. 참조만 profile에 결합하며 override 없는 기존 binding은 유지했다. 승인 있는 쓰기는 아직 구현하지 않았다.
- HTTP 확장4 변경 추적: [PR #142](https://github.com/PlateerLab/xgen-cli/pull/142), 구현 코드 `67d1d40`. PR의 CI 결과와 merge 상태를 정본으로 참조한다.

- 다음 범용 작업(사용자 승인, 2026-10-31): 명세별 HTTP 지원 범위를 한번에 보는 `tools check-http` 진단을 진행한다. 개별 describe/execution의 classifier를 재사용하고 저장된 조회 지정 정책을 적용한다. 판정 범위는 import된 도구이며 실제 API·인증·모델 검증과 구분한다.
- HTTP 지원 보고서(ADR-0085, 구현·로컬 검증 완료): 전체 지원/거절·제외 이유 집계, 이름순 bounded page, artifact/policy digest를 제공한다. assets/calendar 설계와 shipment/sensor 실제 CLI 대조에서 개별 describe와 일치했고, bearer 환경변수가 없어도 API·모델 호출0회였다. 외부 통합 과업 수는 계속 미확정이다.
- HTTP 지원 보고서 검증: Rust784개(4 ignored), pinned backend+컴파일 CLI Python159개(7 skipped), Clippy·fmt·protocol·문서·workflow 검사 통과. 독립 diff 검토의 policy 전달·pagination 가설은 실제 native 정책 주입·행 크기 상한·마지막 페이지 검사와 대조했다. Run 계약/profile·HTTP 지원 규칙은 바꾸지 않았다.
55 changes: 55 additions & 0 deletions crates/xgen-cli/src/tool_graph_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,9 @@ def dispatch(request):
raise WorkerError("http_read_operation_invalid")
return {"ok": True}
graph = graph_from_artifact(envelope["artifact"])
if operation == "check_http":
return http_read_report(envelope, [tool.to_dict() for tool in graph.tools.values()],
request.get("http_policy"), request.get("offset", 0), request.get("limit", 100))
if operation in ("describe", "describe_view", "execution_contract"):
tool = graph.tools.get(request["tool"])
if tool is None:
Expand Down Expand Up @@ -646,6 +649,58 @@ def http_read_contract(artifact, tool, policy=None):
return {"supported":False,"error":"http_original_contract_invalid"}


def http_read_report(envelope, tools, policy, offset, limit):
# Diagnostic projection of the same contract classifier used by describe and execution.
if type(offset) is not int or type(limit) is not int or offset < 0 or not 1 <= limit <= 500:
raise WorkerError("invalid_http_report_page")
tools = sorted(tools, key=lambda tool: tool["name"])
if offset > len(tools):
raise WorkerError("invalid_http_report_page")
supported, excluded_count, reasons, rows = 0, 0, collections.Counter(), []
excluded_reasons = collections.Counter()
end, page_bytes, page_full = offset, 0, False
for index, tool in enumerate(tools):
result = http_read_contract(envelope["artifact"], tool, policy)
supported += int(result["supported"])
excluded = result.get("contract", {}).get("excluded_parameters", [])
excluded_count += len(excluded)
excluded_reasons.update(item["reason"] for item in excluded)
if not result["supported"]:
reasons[result["error"]] += 1
if index < offset or page_full:
continue
metadata = tool.get("metadata", {})
operation_id = None
try:
source = envelope["artifact"]["metadata"]["xgen_http_sources"][metadata["source_url"]]
operation = dereference(source["paths"][metadata["path"]], source, recursive=False)[metadata["method"]]
operation_id = operation.get("operationId")
except (KeyError, TypeError, AttributeError, WorkerError):
pass
row = {"tool": preview(tool["name"], 1024), "operation_id": preview(operation_id, 128) if isinstance(operation_id, str) else None,
"method": preview(metadata.get("method"), 16), "path": preview(metadata.get("path"), 1024),
"supported": result["supported"]}
if result["supported"]:
row.update({"contract_digest": result["contract_digest"], "excluded_parameter_count": len(excluded),
"excluded_parameters": [{"location": item["location"], "name": preview(item["name"], 256), "reason": item["reason"]} for item in excluded[:32]],
"excluded_parameters_complete": len(excluded) <= 32 and all(len(item["name"].encode()) <= 256 for item in excluded)})
else:
row["error"] = result["error"]
size = len(canonical(row))
if len(rows) >= limit or page_bytes + size > 1024 * 1024:
page_full = True
continue
rows.append(row)
page_bytes += size
end = index + 1
return {"ok": True, **summarize(envelope), "report_version": 1, "scope": "imported_tools", "policy_digest": digest(policy),
"connection_present": policy is not None, "credentials_checked": False, "network_checked": False,
"summary": {"total": len(tools), "supported": supported, "unsupported": len(tools) - supported,
"excluded_parameter_count": excluded_count, "unsupported_reasons": dict(sorted(reasons.items())),
"excluded_parameter_reasons": dict(sorted(excluded_reasons.items()))},
"page": {"offset": offset, "limit": limit, "next_offset": end if end < len(tools) else None}, "tools": rows}


def main():
warnings.simplefilter("ignore")
try:
Expand Down
18 changes: 17 additions & 1 deletion crates/xgen-cli/src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ pub enum ToolsCommand {
#[arg(long, default_value_t = 5, value_parser = clap::value_parser!(u8).range(1..=20))]
top_k: u8,
},
/// Inspect HTTP contract support for a saved collection without API or credential access.
CheckHttp {
#[arg(long)]
name: String,
#[arg(long, default_value_t = 0)]
offset: u32,
#[arg(long, default_value_t = 100, value_parser = clap::value_parser!(u16).range(1..=500))]
limit: u16,
},
/// Read the normalized schema and retained API contract of an exact saved tool.
Describe {
#[arg(long)]
Expand Down Expand Up @@ -203,6 +212,13 @@ fn request(command: ToolsCommand) -> Result<Value, &'static str> {
}
json!({"operation": "search", "name": name, "query": query, "top_k": top_k})
}
ToolsCommand::CheckHttp {
name,
offset,
limit,
} => {
json!({"operation":"check_http", "name":name, "offset":offset, "limit":limit})
}
ToolsCommand::Describe {
name,
tool,
Expand Down Expand Up @@ -230,7 +246,7 @@ fn request(command: ToolsCommand) -> Result<Value, &'static str> {
value["root"] = json!(root.to_str().ok_or("invalid_state_home")?);
if matches!(
value["operation"].as_str(),
Some("describe" | "describe_view")
Some("describe" | "describe_view" | "check_http")
) {
let snapshots = crate::graph_discovery::DiscoveryCatalog::discover()
.map_err(|()| "http_catalog_unavailable")?;
Expand Down
50 changes: 50 additions & 0 deletions docs/adr/0085-http-read-support-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# ADR-0085: import된 도구의 HTTP 조회 지원 보고서

- 날짜: 2026-10-31
- 상태: 채택, 구현·로컬 검증 완료
- 범위: 일반 CLI 진단. API 호출 권한이나 실행 계약은 추가하지 않는다.

## 결정

`xgen tools check-http --name COLLECTION [--offset N] [--limit N]`을 추가한다.
CLI의 기존 연결 정책 로딩 경로를 재사용하고 worker의 기존 http_read_contract를 각 도구에 적용한다.
개별 describe·실행 계약과 다른 지원 판정기를 만들지 않는다. GET 지정과 read_operations 목록을
반영하되 인증값·OS secret store를 읽거나 API·모델을 호출하지 않는다.

전체 summary에는 import된 도구 수, supported/unsupported 수, 거절 이유별 operation 수,
제외한 선택 파라미터 수·이유별 개수를 제공한다. unsupported가 있어도 보고서 생성 성공이면 exit0이다.
저장소·snapshot·페이지 요청 오류는 기존 CLI 오류 경로로 실패한다.

행에는 tool·원본 operationId·method·path, 지원 여부, 지원 계약 digest 또는 거절 코드,
선택 파라미터 제외 이름·위치·이유를 제공한다. 원본 operationId는 보존된 명세에서 조회하며
정규화된 도구 이름을 operationId로 추측하지 않는다. 긴 문자열은 기존 preview(text/omitted) 형식으로
명시적으로 자른다. schema·응답 예시·인증 참조·토큰 값은 보고서에 넣지 않는다.

scope는 imported_tools다. graph-tool-call import에서 누락된 원본 operation까지 센 결과가 아니다.
연결이 없을 때는 기존 describe와 같은 GET 계약 판정이며 실제 호출 승인을 뜻하지 않는다.
connection_present·credentials_checked=false·network_checked=false·execution_enabled=false로
진단의 경계를 명시한다. 지원 판정은 credential 유효성·입력값 검증·API 건강 상태·업무 성공 보장이 아니다.

## 크기와 일관성

도구 이름으로 정렬하며 offset/limit을 제공한다(기본100개, 최대500개). 한 페이지의 행 합계는1MiB,
제외 항목은 도구당32개·이름256 bytes로 제한한다. 도구 이름/경로1024 bytes, operationId128 bytes,
method16 bytes의 preview를 사용한다. 제외 목록의 count/complete를 함께 보낸다.

summary는 페이지 밖 도구까지 모두 집계한다. next_offset은 실제 반환한 행 다음을 가리키므로
행 개수 제한보다 byte 제한에 먼저 닿아도 건너뛰지 않는다. 마지막 offset==total 요청은 빈 페이지와
전체 summary를 반환한다. 페이지를 합치는 호출자는 artifact_digest와 policy_digest가 같은지 확인한다.
policy_digest는 인증 참조가 없는 GET/operation 지정 map 또는 미연결 null만 해시한다.

이 명령은 모델 Capability가 아니므로 Capability 버전·Run manifest·execution profile을 바꾸지 않는다.

## 검증

assets(OpenAPI3)·calendar(Swagger2) 설계 fixture에서 개별 classifier와 summary·페이지 결과를
대조한다. POST 지정 전후·선택 파라미터 제외·빈 목록·잘못된 페이지·마지막 빈 페이지·긴 제외 이름과
1MiB 페이지 경계를 검증한다. shipment/sensor 보류 fixture의 실제 import→connect→check-http를
컴파일 CLI와 pinned backend로 검증한다. bearer 환경변수가 없어도 점검되며 API/모델 호출은0회다.

실제 외부 명세의 실행 가능 과업 수, LLM 완료율, API 응답·인증의 유효성은 측정하지 않는다.

Rust784개(4 ignored), pinned backend+컴파일 CLI Python159개(7 skipped), Clippy·fmt·protocol·문서·workflow 검사를 통과했다.
22 changes: 22 additions & 0 deletions docs/development/http-read-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,28 @@ xgen tools remove --name my-system # collection, 연결, 저장된 토큰
- 연결 저장소가 사용자 전용이 아니거나, 연결 파일이 symlink거나, 저장소를 읽을 수 없으면(권한 오류를 "연결 없음"으로 보지 않는다) 아무것도 지우지 않고 실패한다.
- 연결을 바꾸면 그 연결로 시작한 Run은 재개 시 기존처럼 거절된다.

## HTTP 지원 범위 점검

```bash
xgen tools check-http --name my-system
xgen tools check-http --name my-system --offset 0 --limit 100
```

개별 `describe`와 같은 판정으로 import된 도구의 지원·거절·선택 파라미터 제외를 JSON으로 모은다.
`summary`는 전체 집계이고 `tools`는 이름순 페이지다. 제외 파라미터 수는 지원된 계약에서 실제로 제외한 항목만 센다. 다음 페이지는 반환된 `page.next_offset`을
`--offset`으로 넣는다. 값이 null이면 끝이다. 기본100개·최대500개이며 행 byte 한도에 먼저 닿으면
더 적게 반환할 수 있다. 페이지를 합칠 때 `artifact_digest`와 `policy_digest`가 같은지 확인한다.

연결이 있으면 저장된 GET/POST 조회 지정 정책을 반영한다. 연결이 없으면 기존 describe와 같은
GET 계약 판정이며 `connection_present=false`다. tool·원본 operationId·method·path는
`text`/`omitted` preview이므로 잘린 이름으로 operation 지정을 시도하지 않는다.
제외한 파라미터는 count·complete와 함께 이름·위치·이유를 제공한다.

이 명령은 토큰을 읽거나 API·모델을 호출하지 않는다. `supported`는 **계약 판정**이며 인증·실제 입력값·
API 응답의 검증이나 과업 성공을 뜻하지 않는다. `scope=imported_tools`이므로 import에서 빠진 원본
operation의 수까지 확인한 결과도 아니다. unsupported 도구가 있어도 보고서 생성에 성공하면 exit0이다.
[ADR-0085](../adr/0085-http-read-support-report.md).

## Run별 bearer 주입

```bash
Expand Down
7 changes: 7 additions & 0 deletions docs/development/tool-integration-todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,10 @@
[ADR-0084](../adr/0084-run-bearer-overrides.md), [PR #142](https://github.com/PlateerLab/xgen-cli/pull/142).
5. **별도 설계, 대회 후:** 승인 있는 POST/PUT/PATCH/DELETE 쓰기·host Idempotency-Key·NonIdempotent
Unknown의 no-retry. 1~4 완료 뒤 진행하며 이번 조회 확장에 포함하지 않는다.

### HTTP 지원 범위 점검 (2026-10-31)

- **구현·로컬 검증 완료:** `tools check-http --name COLLECTION`으로 import된 도구의 지원·거절·선택 파라미터 제외를 집계한다.
- 기존 classifier·저장된 조회 정책을 재사용하며 API·모델·credential 조회는 하지 않는다. 실제 실행 가능 과업 수와 구분한다.
- 원본 operation이 import에서 누락됐는지와 실제 인증·API 응답·기존 에이전트 비교는 별도 통합 검증이다.
- [ADR-0085](../adr/0085-http-read-support-report.md), [사용법](http-read-tools.md#http-지원-범위-점검).
Loading