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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ coverage/
.serena/
shrimp/
experiments/routes-oracle/target/
experiments/client-oracle/target/
37 changes: 36 additions & 1 deletion HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,42 @@

세션을 이어받는 에이전트가 먼저 읽는 문서입니다.

## 진행 중 — feature/server-routes (2026-09-30, API 영향 프로그램 Phase 7 후속)
## 진행 중 — feature/client-routes (2026-09-30, API 영향 프로그램 개선 #3 Rust 부분)

`rustograph routes --role client`(isthmus http `route-call` 생산자). 규칙·근거·오라클 표는 `docs/HTTP-CLIENT.md`.

- **코드** — `src/source/routes/client.rs`(멤버·의존 버전, 문서 조립, 한계) + `client/{index,scan}.rs`(색인과 두 단계
스캐너: 구조체 필드 값 수집 → 호출 사실), `compose.rs`(url-compose 규칙의 순수 구현 — `UrlVal`, WHATWG concat/join,
`http::Uri` concat, 마스킹), `wrappers.rs`(http-wrappers v1 파싱·동사 바인딩). 위치 열은 계약대로 UTF-8 바이트다
(`Ctx::locate_utf8`). **서버 `route-decl`의 `Ctx::locate`는 UTF-16 열을 낸다 — GRAPH-EXCHANGE는 UTF-8 바이트를
요구하므로 비ASCII 줄에서 어긋난다(이번 범위 밖, 후속 수정 후보).**
- **인식** — reqwest(`get`·`blocking::get`·Client 동사·`request`·`Request::new`), ureq 2/3 자유 함수·Agent, 선언된
래퍼(`owner::name` = 정점 ID, 구조체 리터럴 생성자는 이름 = 타입 이름). 수신자는 구문 타입 추론(생성자·주석·필드
타입·반환 타입·static 타입, `Arc`·`LazyLock` 등 벗김). 증명하지 못한 수신자는 사실 없이 센다.
- **검증** — 오라클 41 시나리오: 일치 37 · dynamic 3 · 요청 없음 1 · 불일치 0
(reqwest 0.13.5·ureq 3.4.2·url 2.5.8, scratch에서 reqwest 0.12.28도 같음). 오라클이 확인한 것: `Url::join`의 마지막
세그먼트 교체, WHATWG `//` 보존·점 세그먼트 제거, **ureq 3의 점 세그먼트 보존**, ureq 3이 http 요청도 프록시에
CONNECT를 연다(기록 서버가 터널을 수락). 커버리지 92.14%, stable clippy(1.98.1) 0.
- **벡터** — isthmus `3a45450`(#133) 재벤더링. url-compose `producer` 41 + `producer:rustograph` 7 = 48/48. 새 규칙에 맞춰
미상 base `rfc3986` 상대 참조의 `..`·빈 참조를 dynamic + `ambiguous-base-join:`으로 바꿨다. `scope.dynamic-*`(dynamicScope,
서버 선언 선택 필드)는 내지 않으므로 분류만 했다. 결합 이름은 isthmus가 `Url::join` = `rfc3986`으로 정했고 reqwest·ureq는
base가 없어 전체 URL 규칙이다(제안했던 `whatwg-concat` 등은 불필요).
- **e2e(scratch, 무패치 isthmus `3a45450`)** — workspace trace(`server` = axum08 fixture + `reach --roots-from`,
`client` = fixture-client + `impact --format language-traversal --roots-from`, link match hosts `api.example.com`,
selection 3 routes): 3 routes · 3 calls · 3 handlers · 3 client symbols. `POST /api/items` — client
`client_app::api::ApiClient::create_item`(api.rs:38, exact) → handler `axum_app::handlers::items::create` → client 역방향
`struct_field_plus`(depth 1, candidate — syn 메서드 호출 팬아웃). `GET /api/items/{}`는 `get_item` → `items::show`,
`GET /api/search`는 `search` → `handlers::search`. base 앵커 호출 10건은 host가 없어 `unattributed-calls-omitted`.
isthmus `c395c59`(직전 main)는 같은 client 문서를 "Fact kind is not valid for platform"으로 거부했다.
- **GLM 리뷰** — 재현 후 수정 2건: 상수 사슬 평가가 깊이를 새로 시작해 긴 사슬(3,000개)에서 스택 넘침 → 깊이를 이어
모르는 값으로 낮춤(회귀 테스트는 수정 전 코드에서 넘침을 확인), authority가 `_` host·빈 포트를 거부 → WHATWG대로 받음.
반박 4건: Slack 웹훅 전체 마스킹(벡터 `mask/slack-webhook`이 `/{}/{}/{}/{}`), `--service` 없는 사실 service(계약상 유효
service는 사실 값), 호출 0건도 `http-wrapper-unresolved:`(HTTP-WRAPPERS 생산자 의무), 중첩 서식 `{:{}}`(Rust 문법 아님).
- 알려진 근사: 필드 수정 감지는 소유 타입을 모르면 이름 단위(보수적), `Url` 값의 `set_path` 등은 지역·필드를 믿지 않게
만들 뿐 새 값을 계산하지 않는다, 선언된 구조체 래퍼 값을 받아 다른 래퍼를 부르는 함수(`execute`)는 동사 동적 dynamic
사실을 하나 더 낸다, ureq 3에서 `http::Uri`가 거부하는 문자(공백 등)가 든 리터럴도 사실로 낸다.

## 직전 — feature/server-routes (2026-09-30, PR #23 머지됨)

`rustograph routes --role server`(isthmus http `route-decl` 생산자)와 수확 이름 해석 결함 수정.

Expand Down
43 changes: 40 additions & 3 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ rustograph graph --target x86_64-pc-windows-msvc # cfg(트리플) 평가
rustograph mcp # MCP stdio 서버 — 에이전트가 되묻는 통로
rustograph schema --dir . --out schema-facts.json # isthmus persistence 사실
rustograph routes --role server --out routes.json # isthmus http route-decl(axum·actix-web)
rustograph routes --role client --wrappers http-wrappers.json --out calls.json # route-call(reqwest·ureq)
rustograph reach mycrate::api::list_users # isthmus language-traversal(정방향)
rustograph impact --format language-traversal --roots-from schema-facts.json # 역방향
```
Expand Down Expand Up @@ -165,6 +166,42 @@ import할 때만 인정합니다. 파싱 실패 파일·문법이 다른 `table!
그 기록을 `cargo test`가 오프라인으로 대조합니다. isthmus 공유 벡터는
`conformance/`에 잠금 파일과 함께 벤더링했습니다.

## 클라이언트 호출 — `routes --role client`

`rustograph routes --role client`는 코드가 만드는 HTTP 요청마다 `route-call`
사실 하나를 담은 isthmus `bridge-facts` v1 문서(`platform: "rust"`,
`target: "http"`, `roles: ["client"]`)를 냅니다. `symbol.usr`는 호출을 감싼
함수·메서드이고 `impact`의 정점 ID와 같아서 `isthmus trace`가 호출부에서 그
호출부에 기대는 클라이언트 코드로 이어 갑니다.

- **reqwest**(0.13, 0.12도 확인): `reqwest::get`·`blocking::get`,
`Client`·`blocking::Client`의 동사 메서드, `request(Method::X, url)`,
`Request::new`. 문자열은 `url::Url::parse`(WHATWG)로 해석합니다 — 점
세그먼트는 지우고 `//`는 남깁니다.
- **ureq** 3(2.x는 소스 기준): 자유 함수와 `Agent` 메서드. ureq 3은
`http::Uri`로 해석해 점 세그먼트를 남깁니다.
- **URL 조립**: 리터럴, `format!`(위치·이름·인라인 인자), `concat!`, `+`,
상수·static·연관 상수, 지역 변수(그림자 추적, 수정되는 이름은 믿지 않음),
`Url::parse(..)?.join(..)`(RFC 3986 병합 — `…/v2/catalog` + `tags`는
`/v2/tags`), 모든 생성자가 같은 리터럴·상수로 채우는 구조체 필드 base.
그 밖은 dynamic 사실(`channel: null`, 증명한 경우 마스킹한 `channelPrefix`)
이나 센 한계입니다.
- **래퍼**: isthmus `http-wrappers` v1 파일에 선언한 함수·메서드·구조체
리터럴 엔드포인트(`"language": "rust"`, `owner::name`이 rustograph 정점
ID)는 선언한 동사·앵커로 호출 사실이 됩니다.
- **센 공백**: 모델링하지 않는 클라이언트(hyper client·surf·awc·isahc 등),
클라이언트로 증명하지 못한 수신자의 요청, 클라이언트가 거부하는 상대
URL, 선언되지 않은 래퍼 싱크, 풀리지 않는 선언은 `route-call-coverage:`·
`ambiguous-base-join:`·`http-wrapper-undeclared:`·`http-wrapper-unresolved:`
한계입니다.

규칙(`Url::join`은 isthmus `rfc3986` 결합, reqwest·ureq는 base URL이 없어
전체 URL 규칙)과 오라클 표는 [docs/HTTP-CLIENT.md](docs/HTTP-CLIENT.md)에 있습니다. 모의 서버
오라클(`experiments/client-oracle/`)이 fixture를 진짜 reqwest·ureq·url로
컴파일해 로컬 서버가 받은 요청을 기록합니다 — 41개 시나리오 불일치 0, 기록은
`cargo test`가 오프라인으로 대조합니다. isthmus `url-compose` 벡터의
`producer`·`producer:rustograph` 사례 48건을 모두 통과합니다.

## 순회 문서 — `reach` / `impact --format language-traversal`

isthmus [`language-traversal` v1](https://github.com/ictechgy/isthmus/blob/main/docs/LANGUAGE-TRAVERSAL.md)
Expand All @@ -187,9 +224,9 @@ isthmus [`language-traversal` v1](https://github.com/ictechgy/isthmus/blob/main/
- 그래프 정점이 아닌 root는 `symbol` 없이 싣고 `root-not-found:` limitation을
더한 문서를 쓴 뒤 64로 끝납니다.

isthmus는 `platform: "rust"` 문서의 `route-decl`을 받지 않아 Rust 핸들러에서
시작하는 route 선택 `trace`는 아직 불가능합니다. relation·심볼 선택(역방향
순회)은 지금 동작합니다.
isthmus는 Rust `route-decl`과(isthmus #133부터) `route-call` 문서를 받아,
workspace `trace`가 reqwest 클라이언트를 axum·actix-web 핸들러와 잇고
`impact`로 클라이언트 코드까지 이어 갑니다.

## 개발

Expand Down
50 changes: 47 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ rustograph schema --dir . --out schema-facts.json
rustograph routes --role server --dir . --out routes.json
rustograph reach --roots-from routes.json # handler usrs are graph vertices

# Emit isthmus http route-call facts for reqwest / ureq clients
rustograph routes --role client --dir . --wrappers http-wrappers.json --out calls.json
rustograph impact --format language-traversal --roots-from calls.json

# isthmus language-traversal v1 for `isthmus trace` (many roots, one pass)
rustograph reach mycrate::api::list_users mycrate::api::create_user
rustograph impact --format language-traversal --roots-from schema-facts.json
Expand Down Expand Up @@ -266,6 +270,46 @@ the real crates and probes them in-process: 100% precision and recall on
all three fixtures, recorded and checked offline by `cargo test`. The
isthmus conformance vectors are vendored under `conformance/` with a lock.

## Client calls — `routes --role client`

`rustograph routes --role client` emits an isthmus `bridge-facts` v1
document with `platform: "rust"`, `target: "http"`, `roles: ["client"]` and
one `route-call` fact per HTTP request the code builds. `symbol.usr` is the
enclosing function or method — the same vertex id `impact` uses, so
`isthmus trace` can continue from a call site into the client code that
depends on it.

- **reqwest** (0.13; 0.12 checked too): `reqwest::get`, `blocking::get`,
`Client`/`blocking::Client` verb methods, `request(Method::X, url)`,
`Request::new`. Strings go through `url::Url::parse` (WHATWG): dot
segments are removed, `//` is kept.
- **ureq** 3 (2.x read from source): free functions and `Agent` methods.
ureq 3 parses with `http::Uri`, which keeps dot segments.
- **URL building**: literals, `format!` (positional, named and inline
arguments), `concat!`, `+`, consts/statics/associated consts, locals
(shadowing-aware, mutated names untrusted), `Url::parse(..)?.join(..)`
(RFC 3986 merge — `…/v2/catalog` + `tags` is `/v2/tags`), and a base URL
held in a struct field when every constructor fills it with the same
literal or const. Anything else stays a dynamic fact (`channel: null`, a
masked `channelPrefix` when proven) or a counted limitation.
- **Wrappers**: functions, methods and struct-literal endpoints declared in
an isthmus `http-wrappers` v1 file (`"language": "rust"`, `owner::name` is
the rustograph vertex id) become calls with the declared verb and anchor.
- **Measured gaps**: unmodelled clients (hyper client, surf, awc, isahc, …),
requests sent from a receiver that is not a proven client, relative URLs
the client rejects, undeclared wrapper sinks and unresolved declarations
are `route-call-coverage:` / `ambiguous-base-join:` /
`http-wrapper-undeclared:` / `http-wrapper-unresolved:` limitations.

The rules (`Url::join` is the isthmus `rfc3986` join; reqwest and ureq have
no base URL, so full-URL rules apply) and the oracle table are in
[docs/HTTP-CLIENT.md](docs/HTTP-CLIENT.md). A mock-server oracle
(`experiments/client-oracle/`) compiles the fixture against the real
reqwest/ureq/url crates and records every request at a local server: 41
scenarios, 0 mismatches, checked offline by `cargo test`. All 48
`producer`/`producer:rustograph` cases of the isthmus `url-compose` vectors
pass.

## Traversal documents — `reach` / `impact --format language-traversal`

```bash
Expand Down Expand Up @@ -304,9 +348,9 @@ strings as `symbol.usr` in `schema`.
`symbol`, a `root-not-found:` limitation is added, and the command exits
`64` after writing the document.

isthmus rejects `route-decl` facts from `platform: "rust"` documents, so
Rust handlers cannot yet start a route-selection `trace`; relation and
symbol selections (reverse traversal) work today.
isthmus accepts Rust `route-decl` and (since isthmus #133) `route-call`
documents, so a workspace `trace` joins a reqwest client to an axum/actix-web
handler and continues into the client code with `impact`.

## Agent output contract

Expand Down
6 changes: 3 additions & 3 deletions conformance.lock
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"commit": "76b6141e71c84e0ab1026ad1f18f910b9d966dc8",
"commit": "3a4545088e8eaf1095b94765a407a9a0b5d823f2",
"files": {
"http-dispatch.json": "efeecae0622ac8ee4185d10ea117fb9c3c2b2dc504137cae0abd1841f3414f47",
"http-limitation-scope.json": "757200f40b1fa54a30b9486ff24bf5f89c2f7bc4fac26eb9a79d62004a1c847e",
"http-limitation-scope.json": "a7c6c3916bbf6991f24e2640242fdbe71c881858b47b90d74fba2b832f4f4d3c",
"http-template.json": "770f3a79986560579c94b2548d392eb66ef877dc95b6284211551eeb27cf53e6",
"url-compose.json": "afb689685e3dc987d37ce6b8ff32088604956a67dfe8a7e1556aa462f09a1c7a"
"url-compose.json": "ef2ab9523906ec13d383752910e2feca406581b1f85a6bac8fb3b57e97966505"
},
"format": "isthmus-conformance-lock",
"source": "https://github.com/ictechgy/isthmus",
Expand Down
10 changes: 6 additions & 4 deletions conformance/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
# isthmus 공유 적합성 벡터

isthmus(`76b6141e71c84e0ab1026ad1f18f910b9d966dc8`)의 `conformance/`를 그대로 가져온 사본이다. 정본은 isthmus가
isthmus(`3a4545088e8eaf1095b94765a407a9a0b5d823f2`)의 `conformance/`를 그대로 가져온 사본이다. 정본은 isthmus가
소유하며, 이 디렉터리 파일을 직접 고치지 않는다. 갱신할 때는 isthmus main의 파일과 `SHA256SUMS`를 함께 다시
복사하고(새 suite 파일 포함) 저장소 루트의 `conformance.lock`에 커밋과 파일별 sha256을 적은 뒤
`cargo test --test routes`로 확인한다.

rustograph가 실행하는 사례(생산자 대상, `rustograph routes --role server`):
rustograph가 실행하는 사례(생산자 대상, `rustograph routes --role server|client`):

| suite | ruleId | 검사 |
|---|---|---|
| `http-template` | `template.grammar` | 정규 문법 검사기(`source::routes::template::template_problem`)가 소비자와 같은 판정·거부 사유를 낸다 |
| `http-template` | `template.normalize` | URI 경로 정규화(`normalize_uri_path`)가 같다 |
| `http-dispatch` | `dispatch.validate` | `order` 검증기(`source::routes::validate::order_problem`)가 소비자와 같이 판정한다. fixture 출력에도 적용한다 |
| `http-limitation-scope` | `scope.validate` | 스코프 검증기(`scope_problem`)가 소비자와 같이 판정한다(생산 문서가 거부되지 않게) |
| `url-compose` | `compose.*`·`wrapper.*`(`producer` 41건 + `producer:rustograph` 7건) | 클라이언트 조립(`source::routes::compose`)과 래퍼 동사 바인딩(`source::routes::wrappers`)이 같은 결과를 낸다(`tests/client_routes.rs`). `dio-concat`은 결과가 같은 WHATWG 문자열 연결로, `wrapper.location`은 실제 스캐너로 확인한다 |

건너뛰는 사례와 이유: 소비자 전용 사례(`match.*`, `dispatch.match`, `dispatch.shadow`, `scope.applies` — 적용 판정은
소비자가 한다), 다른 생산자의 프레임워크 변환(`framework.openapi.*`, `framework.spring.*`), `url-compose` 전체(클라이언트
`route-call` 조립 규칙이며 rustograph는 서버 선언만 낸다).
소비자가 한다), 다른 생산자의 프레임워크 변환(`framework.openapi.*`, `framework.spring.*`), `url-compose`의 다른 생산자
전용 사례(`producer:kartograph`·`producer:gartograph`·`producer:pythograph`), `scope.dynamic-validate`·
`scope.dynamic-applies`(dynamic 선언의 선택 필드 `dynamicScope` — rustograph는 내지 않는다).
4 changes: 2 additions & 2 deletions conformance/SHA256SUMS
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
efeecae0622ac8ee4185d10ea117fb9c3c2b2dc504137cae0abd1841f3414f47 http-dispatch.json
757200f40b1fa54a30b9486ff24bf5f89c2f7bc4fac26eb9a79d62004a1c847e http-limitation-scope.json
a7c6c3916bbf6991f24e2640242fdbe71c881858b47b90d74fba2b832f4f4d3c http-limitation-scope.json
770f3a79986560579c94b2548d392eb66ef877dc95b6284211551eeb27cf53e6 http-template.json
afb689685e3dc987d37ce6b8ff32088604956a67dfe8a7e1556aa462f09a1c7a url-compose.json
ef2ab9523906ec13d383752910e2feca406581b1f85a6bac8fb3b57e97966505 url-compose.json
Loading
Loading