isthmus-cli 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.ko.md +49 -28
  2. package/README.md +55 -31
  3. package/Skills/isthmus/SKILL.md +93 -4
  4. package/dist/cli/command-support.d.ts +1 -1
  5. package/dist/cli/command-support.js +2 -2
  6. package/dist/cli/command-support.js.map +1 -1
  7. package/dist/cli/impact-command.d.ts +5 -0
  8. package/dist/cli/impact-command.js +111 -0
  9. package/dist/cli/impact-command.js.map +1 -0
  10. package/dist/cli/main.js +15 -0
  11. package/dist/cli/main.js.map +1 -1
  12. package/dist/cli/preflight-command.d.ts +5 -0
  13. package/dist/cli/preflight-command.js +83 -0
  14. package/dist/cli/preflight-command.js.map +1 -0
  15. package/dist/cli/runtime-command.d.ts +5 -0
  16. package/dist/cli/runtime-command.js +42 -0
  17. package/dist/cli/runtime-command.js.map +1 -0
  18. package/dist/cli/runtime-json-reader.d.ts +12 -0
  19. package/dist/cli/runtime-json-reader.js +37 -0
  20. package/dist/cli/runtime-json-reader.js.map +1 -0
  21. package/dist/exchange/impact-selection.d.ts +13 -0
  22. package/dist/exchange/impact-selection.js +35 -0
  23. package/dist/exchange/impact-selection.js.map +1 -0
  24. package/dist/exchange/kartograph-impact.d.ts +4 -0
  25. package/dist/exchange/kartograph-impact.js +169 -0
  26. package/dist/exchange/kartograph-impact.js.map +1 -0
  27. package/dist/exchange/messages.d.ts +51 -0
  28. package/dist/exchange/messages.js +126 -0
  29. package/dist/exchange/messages.js.map +1 -0
  30. package/dist/exchange/parse.d.ts +8 -0
  31. package/dist/exchange/parse.js +12 -8
  32. package/dist/exchange/parse.js.map +1 -1
  33. package/dist/exchange/preflight-context.d.ts +71 -0
  34. package/dist/exchange/preflight-context.js +317 -0
  35. package/dist/exchange/preflight-context.js.map +1 -0
  36. package/dist/exchange/producer-impact.d.ts +17 -0
  37. package/dist/exchange/producer-impact.js +226 -0
  38. package/dist/exchange/producer-impact.js.map +1 -0
  39. package/dist/exchange/runtime.d.ts +69 -0
  40. package/dist/exchange/runtime.js +160 -0
  41. package/dist/exchange/runtime.js.map +1 -0
  42. package/dist/join/message-address.d.ts +7 -0
  43. package/dist/join/message-address.js +37 -0
  44. package/dist/join/message-address.js.map +1 -0
  45. package/dist/join/messages.d.ts +23 -0
  46. package/dist/join/messages.js +80 -0
  47. package/dist/join/messages.js.map +1 -0
  48. package/dist/report/diff.js +4 -3
  49. package/dist/report/diff.js.map +1 -1
  50. package/dist/report/impact.d.ts +62 -0
  51. package/dist/report/impact.js +168 -0
  52. package/dist/report/impact.js.map +1 -0
  53. package/dist/report/preflight-runtime.d.ts +42 -0
  54. package/dist/report/preflight-runtime.js +193 -0
  55. package/dist/report/preflight-runtime.js.map +1 -0
  56. package/dist/report/preflight-view.d.ts +146 -0
  57. package/dist/report/preflight-view.js +189 -0
  58. package/dist/report/preflight-view.js.map +1 -0
  59. package/dist/report/preflight.d.ts +104 -0
  60. package/dist/report/preflight.js +296 -0
  61. package/dist/report/preflight.js.map +1 -0
  62. package/dist/report/runtime-impact.d.ts +38 -0
  63. package/dist/report/runtime-impact.js +94 -0
  64. package/dist/report/runtime-impact.js.map +1 -0
  65. package/dist/report/runtime.d.ts +54 -0
  66. package/dist/report/runtime.js +137 -0
  67. package/dist/report/runtime.js.map +1 -0
  68. package/dist/report/sorted-json.d.ts +1 -1
  69. package/dist/report/sorted-json.js +2 -2
  70. package/dist/report/sorted-json.js.map +1 -1
  71. package/docs/BRIDGE-MESSAGES.md +102 -0
  72. package/docs/GRAPH-EXCHANGE.md +255 -0
  73. package/docs/IMPACT.md +96 -0
  74. package/docs/PREFLIGHT.md +295 -0
  75. package/docs/RUNTIME.md +172 -0
  76. package/docs/TOOLCHAIN.md +101 -0
  77. package/package.json +11 -2
  78. package/scripts/build-preflight-toolchain.mjs +186 -0
  79. package/scripts/capture-preflight.mjs +392 -0
  80. package/scripts/run-child.mjs +16 -0
package/docs/IMPACT.md ADDED
@@ -0,0 +1,96 @@
1
+ # 변경 전 브리지 영향 점검
2
+
3
+ `impact`는 현재 개발 소스에 추가된 명령이다. npm 0.5.0 발행본에는 없으며, checkout에서
4
+ `npm ci && npm run build` 후 `node dist/cli/main.js impact ...`로 실행한다.
5
+
6
+ ```bash
7
+ isthmus impact --file ios/Runner/CameraPlugin.swift dart.json swift.json
8
+ isthmus impact --symbol 'CameraPlugin.handle' dart.json swift.json --compact
9
+ isthmus impact --changes changes.json dart.json swift.json --strict
10
+ ```
11
+
12
+ 파일·심볼·변경 목록 중 한 가지 방식만 선택한다. 파일은 producer의 `location.path`와
13
+ 같은 프로젝트 상대 경로다. 심볼은 `qualifiedName` 또는 USR과 정확히 일치하며,
14
+ 같은 이름이 여러 위치에 있으면 모두 보고한다. 선택 값에 경로 정규화·접두 매칭·rename
15
+ 추론을 적용하지 않는다. 심볼의 전역 유일성을 이름만으로 주장하지 않는다.
16
+
17
+ ## 변경 목록 입력
18
+
19
+ ```json
20
+ {
21
+ "format": "isthmus-changes",
22
+ "version": 1,
23
+ "files": ["ios/Runner/CameraPlugin.swift", "lib/camera.dart"],
24
+ "symbols": ["CameraPlugin.handle"]
25
+ }
26
+ ```
27
+
28
+ 두 목록은 합집합이며 하나는 생략 가능하다. 정규화 전 합계 1~10,000개를 허용한다.
29
+ 파일은 bridge-facts의 상대 경로 조건, 심볼은 안전한 비어 있지 않은 문자열 조건을 따른다.
30
+ 중복 제거 후 문자열 순으로 정렬한다. 알 수 없는 필드는 버린다. 입력 파일당 16 MiB,
31
+ 변경 목록과 bridge-facts 전체 합계 64 MiB UTF-16 텍스트 길이 예산을 적용한다.
32
+
33
+ ## 결과와 해석
34
+
35
+ `isthmus-impact` v1은 다음을 보존한다.
36
+
37
+ - `project`: 증거 상대 경로의 기준. CLI 실행 디렉터리와 동일하다고 추측하지 않는다.
38
+ 파일 존재를 확인하지 않은 snapshot 위치를 실제 로컬 파일 링크로 만들지 않는다.
39
+
40
+ - `selection` / `unmatchedSelectors`: 요청 목록과 관찰하지 못한 선택. 삭제된 코드는
41
+ 삭제 전 snapshot으로 조회한다. 새 snapshot에서 없다는 것은 영향이 없다는 증거가 아니다.
42
+ - `selectedFacts`: 실제 선택된 위치·플랫폼·target·종류·심볼·동적 여부. 동적/미귀속 사실도
43
+ 이 목록과 `summary.unresolvedSelectedFacts`에 남는다.
44
+ - `channels`: 관련 생성·등록 위치와 범위를 넓힌 `reason`.
45
+ - `methods`: 메서드별 Dart 호출과 Swift 핸들러. 생성·등록 변경은 그 채널의 모든 메서드로
46
+ 넓힌다(`channel-wiring`). 메서드 변경은 해당 논리 키만 선택한다(`method`).
47
+ - `reviewFiles`: 선택 지점, 관련 호출·핸들러·배선 파일의 중복 없는 목록.
48
+ - `issues`: 기존 check 정책으로 계산한 관련 진단. 다른 채널의 진단은 섞지 않는다.
49
+ - `limitations` / `relevantLimitations`: 전체 한계와 선택에 관련된 한계. 스코프 없는 한계는
50
+ 해당 target 전체에 적용한다. 미관찰·미해석 선택은 범위를 좁힐 수 없어 전체 한계를 보존한다.
51
+ - `inputs`: 생산 도구·버전·플랫폼·target·생성 시각. 파일 입력 순서와 무관하게 정렬한다.
52
+
53
+ `summary.observedFacts`는 전체 입력 fact 수, `selectedFacts`는 중복 제거한 선택 근거 수다.
54
+ `--compact`는 JSON 공백만 줄이며 근거·한계를 생략하지 않는다.
55
+
56
+ | 코드 | 의미 |
57
+ |---|---|
58
+ | 0 | 보고서 생성 성공. `unobserved`도 기본 모드에서는 보고한다. |
59
+ | 1 | `--strict`에서 관련 error·미검증 진단·관련 분석 한계·미해석 선택·미관찰 선택이 남음. |
60
+ | 2 | 읽기·JSON·교환 계약·입력 예산·조인 보류 등 실행 실패. 부분 보고서 없음. |
61
+ | 64 | 인수 또는 직접 입력한 파일/심볼 선택 오류. |
62
+
63
+ 이 명령의 strict는 check strict보다 분석 공백에 엄격하다. 기존 check/diff 동작은 유지한다.
64
+
65
+ ## 런타임 관찰 연결
66
+
67
+ ```bash
68
+ isthmus impact --file lib/dynamic.dart dart.json swift.json \
69
+ --runtime runtime.json --revision current-source-revision --compact
70
+ ```
71
+
72
+ 두 옵션은 함께 지정한다. runtime의 project는 정적 입력과 같아야 하고 revision은 호출자가
73
+ 선언한 분석 revision과 같아야 한다. 정적 bridge-facts v1에는 revision 필드가 없으므로,
74
+ CI/호출자가 같은 소스에서 사실을 생산했다는 문맥을 제공해야 한다. 소비자가 이를 추측하지 않는다.
75
+
76
+ 선택된 호출 파일·정확한 fact 위치 또는 선택된 정적 주소와 관련된 런타임 이벤트를 모은다.
77
+ 동적 Dart 호출에서 관찰된 구체적인 MethodChannel 주소로 Swift 핸들러 후보를 찾으면
78
+ `reason: runtime-observation`으로 관련 메서드와 검토 파일을 넓힌다. 정적 invocation 사실을
79
+ 합성하지 않고 `runtime` 필드에 별도 근거를 둔다. 정적 미해석 수와 기존 진단은 그대로 남는다.
80
+
81
+ 런타임 run의 platform이 ios/macos이고 transport가 MethodChannel인 경우에만 현재 Swift
82
+ 정적 핸들러 후보를 찾는다. native 심볼이나 엔진이 실제 실행됐다고 확정하지 않는다.
83
+ BasicMessageChannel과 다른 OS는 `unsupported`, 정적 후보가 없으면 `unobserved`다.
84
+ revision이 오래되면 이벤트를 연결하지 않고 `stale`로 보존한다.
85
+
86
+ 주소·인스턴스별 호출을 묶고 호출 위치는 20개까지만 보여 주며 `callersOmitted`를 기록한다.
87
+ `reviewFiles`는 표시 생략과 무관하게 관찰한 전체 관련 파일을 유지한다. 런타임 실패·대기·
88
+ 중단·유실·오래된 기록·관련 관찰 부재·정적 후보 공백도 strict 실패 조건이다.
89
+
90
+ ## 현재 경계
91
+
92
+ `scope: "bridge"`, `complete: false`는 항상 명시한다. 현재 기능은 브리지에 직접 등장한
93
+ 코드의 변경 범위를 찾는다. 언어 내부 helper→handler→Dart 화면으로 이어지는 전이 경로,
94
+ 실제 엔진·등록 수명과 테스트 시나리오의 포괄성은 아직 검증하지 않는다. 런타임 관찰은
95
+ 위 방식으로 후보를 넓히는 근거이며, 전체 의존성의 완전성이나 실제 핸들러 실행 증명은 아니다.
96
+ 이들은 [전체 목표](COMPETITIVENESS.md)의 남은 구현이며 이 명령의 존재로 완료 처리하지 않는다.
@@ -0,0 +1,295 @@
1
+ # 언어 간 변경 사전 점검
2
+
3
+ 개발 소스 기능이며 npm 0.5.0 발행본에는 없다. `isthmus preflight`는 JSON만 읽는다.
4
+ 세 도구를 고정 source commit에서 새로 구축하는 방법은 [TOOLCHAIN.md](TOOLCHAIN.md)에 있다.
5
+ 언어 내부 해석과 compiler index 생성은 producer 및 별도 workflow가 맡는다.
6
+ 개발 소스는 Flutter Dart↔Swift/Kotlin의 MethodChannel과 producer가 제공한 사용 관계를 연결한다.
7
+ 선택적 [BasicMessageChannel v2 입력](BRIDGE-MESSAGES.md)을 함께 수집하면 literal 주소와
8
+ Pigeon의 증명된 prefix 후보도 연결한다. 플랫폼별 실제 실행·모든 생성 형태·앱 전체 정확도는
9
+ 별도 검증 범위다. Kotlin 및 Basic producer 확장은 현재 개발 버전이 필요하다.
10
+
11
+ ```bash
12
+ isthmus preflight context.json --strict --compact
13
+ isthmus preflight context.json --summary --limit 20 --strict --compact
14
+ isthmus preflight context.json --explain <exact-producer-symbol-id> --strict --compact
15
+ isthmus preflight context.json --revision <expected-capture-revision> --strict --compact
16
+ isthmus preflight context.json success.json failure.json --expectations checks.json --strict --compact
17
+ ```
18
+
19
+ `impact`는 bridge-facts에서 직접 관련된 경계를 찾는다. `preflight`는 언어별 전이 분석도
20
+ 입력받아 Swift helper→handler→채널→Dart caller→Dart 소비자를 연결한다. 두 명령 모두
21
+ `complete: false`이며 삭제 허가나 영향 범위의 완전성을 보증하지 않는다.
22
+
23
+ ## 입력 계약
24
+
25
+ `isthmus-preflight-context` v1은 다음을 담는다. 예제는 저장소의
26
+ `fixtures/preflight/context.json`이며 실제 앱 입력으로 사용할 수 없는 합성 자료다.
27
+
28
+ - `project`: 두 bridge-facts 문서와 동일한 정규화된 프로젝트 경로.
29
+ - `revision`: 같은 수집의 신원을 나타내는 비어 있지 않은 문자열. 필드만으로 로컬 코드의
30
+ 신선도를 확인할 수 없다. 아래 수집기는 명시된 입력 내용의 SHA-256을 사용한다.
31
+ - `selection`: `dart`·`swift`·`kotlin`별 `{files, symbols}`. 파일은 project 상대 경로이며 이름·USR은
32
+ producer가 해석한다. `{}`는 `noChanges`이고 검증되지 않은 선택과 구분된다.
33
+ - `bridges`: 같은 project의 Dart와 Swift 또는 Kotlin bridge-facts v1. 호출/수신 문서가 모두 필요하며 mixed-targets는 거부한다.
34
+ - `messages`: 선택적 Basic 전용 bridge-facts v2 목록. 주면 같은 project의 Dart와 native 문서가
35
+ 모두 필요하다. v1과 별도 transport로 조인하며 method를 합성하지 않는다.
36
+ - `analyses`: 언어별 `{id, platform, tool, requested, roots, affected, limitations, truncated}`.
37
+ 심볼은 `{id, qualifiedName, kind?, location?}`이고 위치는 producer가 관찰한 소스 위치다. affected 항목은
38
+ `{symbol, via, depth, relationships}`이다. `via`가 가리키는 선행 심볼을 사용하므로 영향을
39
+ 받는 소비자라는 뜻이다. 원래 producer ID를 유지하며 누락된 위치를 만들지 않는다.
40
+ Kotlin/JVM 심볼 위치는 관찰된 `{path, line?, column?}`를 보존한다. bytecode에 없는
41
+ 줄·열 번호를 1로 채우지 않는다. Dart binding과 bridge fact의 위치는 기존처럼 완전한
42
+ line/column을 요구하며, 부분 위치는 Kotlin 분석 심볼에만 허용한다.
43
+ JVM line table의 줄은 함수 선언보다 첫 실행 구문을 가리킬 수 있으므로 선언 시작으로
44
+ 다시 해석하지 않는다.
45
+ - `trigger`: 후속 Dart 분석에만 쓰는 선택적 필드. 브리지에서 도달한 호출자 ID이며, 해당
46
+ 분석의 유일한 symbol 요청과 root에 포함되어야 한다. 초기 선택으로 다시 세지 않는다.
47
+ - `bindings`: Dart fact 위치와 실제 query로 얻은 심볼의 연결. `{platform:"dart", location,
48
+ requested, symbol}`. requested는 fact의 짧은 이름과 같아야 하고 선언과 호출 파일이 같아야
49
+ 한다. query qualifiedName은 전체 graph ID일 수 있어 requested와 같다고 강제하지 않는다.
50
+ - `limitations`: 수집 단계에서 남은 공백. producer 공백·미귀속·truncation도 보고서에서 보존한다.
51
+
52
+ 분석 ID·분석 안의 심볼 ID는 유일해야 한다. 부모 누락·순환을 만드는 depth 불일치·다른
53
+ 프로젝트·모르는 플랫폼을 거부한다. 초기 requested의 합집합은 selection과 정확히 같아야 한다.
54
+ 상한은 context 텍스트 UTF-16 길이 64 Mi, 분석 256개, roots+affected 총 50,000개,
55
+ binding 100,000개, 각 producer depth 128, 관계 문자열 32개, 조합된 근거 1,000,000개다.
56
+
57
+ ## 출력과 해석
58
+
59
+ 보고서는 `isthmus-preflight` v1, `scope: cross-language-impact`다.
60
+
61
+ - `roots`, `affected[].via/depth/relations`로 가장 가까운 변경 root까지 경로를 복원한다.
62
+ `language` 관계와 실제 fact를 가리키는 bridge 관계를 구분한다.
63
+ - `boundaries`는 영향받는 소비자 경계와 검토할 의존 경계를 구분한다. Dart 호출자를 바꾼다는
64
+ 이유로 변경되지 않은 native handler의 다른 호출자에게까지 영향을 전파하지 않는다.
65
+ - Basic의 완전한 handler 범위 귀속 근거가 있으면 실제 dependency/dispatch 후보로 해당
66
+ 경계에만 전파한다. `bridge-message-dependency`는 원래 참조 위치·대상과 선택된 dispatch
67
+ 후보를 보존한다. 등록 함수 직접 변경과 closure 밖 공유 의존 변경은 전체 관련 배선을
68
+ 포함한다. 근거가 없으면 넓은 후보와 `unresolved-message-handler-scope` 공백을 유지한다.
69
+ - `reviewFiles`, `issues`, `limitations`, `bridgeLimitations`와 선택적 `messageLimitations`를 함께 읽는다. 바인딩 부재,
70
+ 후속 Dart 분석 부재, 미관찰 선택, producer truncation은 검토가 필요한 공백이다.
71
+ - compact는 공백만 제거한다. `--strict`는 관련 error·공백·미관찰 선택 또는 revision 불일치에
72
+ 코드 1을 반환하고 JSON을 유지한다. 입력 계약/읽기 실패는 2, 사용 오류는 64다.
73
+ - `noChanges`는 모델링한 소스 선택이 없다는 뜻이다. 설정·리소스 변경 공백이 있으면 strict는
74
+ 여전히 실패한다. 알 수 없는 선택을 영향 없는 변경으로 처리하지 않는다.
75
+
76
+ ### 작은 요약과 개별 경로
77
+
78
+ `--summary`는 `isthmus-preflight-summary` v1이다. 전체 분석의 `summary`·`requiresReview`를
79
+ 유지하고 목록을 `{total, items, omitted}`로 제한한다. 기본 20개, `--limit`은 1~100이며
80
+ 요약에만 쓴다. 생략한 항목도 분석·strict 판정에 포함된다. 원문 producer 근거는 context의
81
+ sidecar와 전체 보고서에서 확인한다. `--compact`는 각 출력의 JSON 공백만 줄인다.
82
+
83
+ `--explain <selector>`는 `isthmus-preflight-explanation` v1이다. 정확한 subject key,
84
+ producer ID, qualifiedName 순서로 찾는다. `result.path`는 root에서 대상까지 **전체 배열**이고
85
+ 각 단계의 relations만 표시 한도가 있다. `found`이면 전체 보고서의 strict 종료 코드를
86
+ 유지하며 `notFound`·`ambiguous`는 JSON과 종료 코드 64를 반환한다. summary와 explain은
87
+ 동시에 쓰지 않는다. 처음에 summary로 찾은 ID를 explain에 전달하면 대형 JSON을 반복해서
88
+ 읽지 않고 필요한 경로를 얻을 수 있다.
89
+
90
+ 두 뷰의 `runtime.verification.declaredScenarioPlatforms`는 선언된 scenario/platform 쌍 수다.
91
+ 통과한 시나리오 수가 아니며 `passedChecks`와도 구분한다. 일부 표시 목록이 비어 있거나
92
+ 잘려도 전체 검토 필요 상태와 근거 공백은 사라지지 않는다.
93
+ runtime route의 candidateKey로 후보 목록을 찾을 수 있으며 `handlers`도 `{total, items, omitted}`로
94
+ native 위치·심볼을 제공한다. 이 omitted는 뷰의 표시 생략까지 포함하며 기존 `handlersOmitted`는
95
+ 전체 runtime 보고서가 먼저 적용한 후보 표시 상한의 생략 수다. 둘을 합산하지 않는다.
96
+
97
+ ## 전이 분석과 runtime 대조
98
+
99
+ `--expectations <checks.json>`를 주면 context 뒤의 위치 인자는 bridge-runtime v1 기록이다.
100
+ 기대만 주고 기록을 생략할 수도 있으며 이는 미관찰 검증으로 남는다. 기록만 주고 기대를
101
+ 생략하면 사용 오류다. 각 기대/기록은 UTF-16 16 Mi, context를 포함한 전체 입력은 64 Mi,
102
+ runtime 문서는 최대 256개다. [런타임 계약](RUNTIME.md)의 독립 기대·실패·미완료 규칙을 재사용한다.
103
+
104
+ - `runtime.verification`: 선언된 시나리오 자체의 검증. 기대 목록과 기록이 과거 revision에서
105
+ 서로 일치하면 이 부분만 passed일 수 있으므로 `runtime.aligned`도 확인해야 한다.
106
+ - `runtime.aligned`: 기대 목록의 revision과 정적 context revision의 일치 여부. 다르면
107
+ `stale-runtime-expectations` 공백을 추가해 strict가 실패한다. 원본 revision을 고쳐 쓰지 않는다.
108
+ - `runtime.routes`: 현재 context revision의 관련 실행 주소. run·scenario·OS·instance·outcome
109
+ 집계를 보존하며 정적 후보는 `candidates`라고 표시한다. 특정 native 심볼이 실제 실행됐다는
110
+ 증명이 아니다. 기존 `affected` 경로나 정적 동적/미귀속 공백은 덮어쓰지 않는다.
111
+ - `runtime.candidates`: 동적 호출로 새로 찾은 native 후보의 실제 fact 위치·심볼. route의
112
+ `candidateKey`로 찾으며 여러 runtime 인스턴스에서도 후보 목록은 한 번만 저장한다.
113
+ 후보는 주소당 20개까지 표시하고 `handlersOmitted`로 생략 수를 알린다. 검토 파일에는
114
+ 표시 상한 밖의 후보도 포함한다. 후보 존재를 실제 native 실행 심볼의 확정으로 바꾸지 않는다.
115
+ Swift producer가 실은 Objective-C fact는 `sourceLanguage: objective-c`를 유지하며,
116
+ Swift 언어 그래프의 신원으로 변환하지 않는다.
117
+ - `selectionReasons`는 주소 일치(`route`)와 검토 파일에서 선언한 caller(`caller-file`)를
118
+ 구분한다. 기록의 caller는 수집기가 명시한 위치이며 주변 선언이나 stack에서 추측하지 않는다.
119
+ caller 표시는 route당 20개까지이며 생략 수를 알리고 reviewFiles에는 모든 관련 파일을 남긴다.
120
+ - `unobservedBoundaries`: 현재 실행 기록이 없는 관련 정적 경계. `uncoveredBoundaries`는
121
+ 현재 기대 시나리오에 들어 있지 않은 관련 경계다. 다른 주소의 통신만 성공하면 이 공백은 남는다.
122
+ - Basic/Pigeon 기록은 v2 messages가 있을 때 Basic 주소에만 연결한다. 같은 이름의
123
+ MethodChannel이 Basic을 덮지 않는다. `matching: prefix` 후보의 실행 관찰도 suffix/instance
124
+ 배선의 정적 공백을 지우지 않는다. v2가 없거나 해당 native 언어 문서가 없으면 정적 연결은 unsupported다.
125
+ runtime 검증과 정적 후보 연결의 지원 범위는 다르며 다른 플랫폼을 추측해 연결하지 않는다.
126
+ - Android 실행은 Kotlin 후보, iOS/macOS 실행은 Swift 후보에만 연결한다. 양쪽 native 문서가
127
+ 함께 있는 경계는 한쪽 실행만으로 모두 관찰됐다고 표시하지 않는다. 같은 주소라도 candidateKey가
128
+ 다를 수 있으므로 원래 키로 조회한다. 조건부 플랫폼 분기가 필요한 앱은 native별 capture를 사용한다.
129
+
130
+ 기대 실패는 `allowedOutcomes`로 명시한다. `runtime.verification.status`가 passed이고
131
+ aligned가 true여도 정적 공백·미관찰/미포함 경계가 남으면 전체 strict는 1이다.
132
+
133
+ 경계의 관찰/기대 포함 여부는 **주소 수준**이다. 같은 주소를 공유하는 기능·인자 분기·호출
134
+ 경로가 모두 실행됐다는 뜻이 아니다. 원하는 기능은 독립 기대 목록에 해당 scenario로 지정해야
135
+ 한다. 알려진 관련 메서드는 각각 대조하며, 메서드가 알려지지 않은 channel-only 경계는 해당
136
+ 채널의 메서드 통신을 배선 관찰로만 인정한다. 그것으로 모든 메서드나 소스 경로의 실행을 보증하지 않는다.
137
+
138
+ ## Android 수집
139
+
140
+ `cartograph` 대신 `kartograph`와 `kartographSnapshot`을 지정하면 Android만 수집할 수 있다.
141
+ 양쪽 native producer를 함께 지정할 수도 있다. Kotlin snapshot은 prepare 단계가 만들며
142
+ 내용을 캐시 지문에 자동 포함한다. `toolInputs`에는 Kartograph 실행 스크립트와 배포의 `lib/`
143
+ 디렉터리를 모두 넣는다. 실행 스크립트가 같아도 실제 JAR이 바뀔 수 있기 때문이다.
144
+
145
+ ```json
146
+ {
147
+ "project": "/path/to/flutter-app",
148
+ "inputs": ["lib", "android/app/src", "android/app/build.gradle.kts", "pubspec.yaml", "pubspec.lock"],
149
+ "toolInputs": ["/path/to/kartograph/bin/kartograph", "/path/to/kartograph/lib", "/path/to/dartograph"],
150
+ "dartograph": ["/path/to/dartograph"],
151
+ "kartograph": ["/path/to/kartograph/bin/kartograph"],
152
+ "kartographSnapshot": "android/app/build/reports/kartograph/debug-snapshot.json",
153
+ "prepare": [["android/gradlew", "-p", "android", ":app:kartographSnapshotDebug"]],
154
+ "messages": true,
155
+ "selection": {"kotlin": {"files": ["android/app/src/main/kotlin/example/CameraPlugin.kt"], "symbols": []}},
156
+ "output": ".isthmus/context.json",
157
+ "cache": ".isthmus/cache.json"
158
+ }
159
+ ```
160
+
161
+ 예제 Gradle task는 앱에 Kartograph plugin을 적용한 경우다. snapshot 출력 경로는 실제
162
+ 프로젝트의 build directory에 맞춘다(Flutter가 build directory를 옮길 수 있다). 별도 snapshot
163
+ 생성 명령을 prepare에 연결해도 된다. 생성 설정·SDK·variant 등 실제 분석에 영향을 주는
164
+ 입력도 선언한다. Git `since`는 `.kt`·`.java` 변경과 rename 양쪽 경로를 선택하며 해당
165
+ producer를 구성하지 않은 플랫폼의 변경은 공백으로 남긴다.
166
+
167
+ Kotlin adapter는 `kartograph-impact` v1의 current 경로와 bytecode/runtime-model 출처를 보존한다.
168
+ base/current를 섞은 입력은 거부한다. 경로 중간 심볼·위치·출력 페이지가 빠졌으면 그 사실을
169
+ 알리고 현재 의존 경로를 지어내지 않는다. 전체 원본은 `.sources.json`에 남긴다.
170
+ adapter는 경로의 node/edge 항목을 합해 1,000,000개까지 읽고 상한 초과는 입력 오류로
171
+ 거부한다. 수집 명령은 producer의 depth 128·출력 10,000개 한도를 사용한다.
172
+
173
+ 앱 내부에 복사한 공개 Pigeon 패키지의 Dart 구현까지 연결하려면 이를 지원하는 개발
174
+ Dartograph의 `dartograph.yaml`에서 분석할 로컬 패키지를 명시한다.
175
+
176
+ ```yaml
177
+ source_packages:
178
+ - vendor/shared_preferences_android
179
+ ```
180
+
181
+ 이는 지정한 패키지의 `lib/`만 추가한다. 앱의 path dependency로 연결하고 `flutter pub get`을
182
+ 실행한 뒤, 설정 파일·패키지 소스·pubspec·package_config를 capture 입력에도 포함한다.
183
+ pub 캐시 전체나 모든 의존 패키지를 분석했다고 간주하지 않는다. 같은 이름의 Dart 후보는
184
+ producer의 실제 ID와 파일 위치로 대조하며 같은 파일 안에서도 모호하면 연결하지 않는다.
185
+
186
+ ## 자동 수집과 CI
187
+
188
+ `scripts/capture-preflight.mjs`는 별도 workflow 진입점이다. 사용자가 작성한 설정의 `prepare`
189
+ 명령을 실행하고 producer JSON을 수집한다. 신뢰하지 않는 저장소가 제공한 명령 설정을
190
+ 그대로 실행하지 않는다. 패키지에는 이 스크립트·run-child·컴파일된 소비자 모듈이 포함된다.
191
+
192
+ ```bash
193
+ # 저장소에서 사용: npm ci와 npm run build 후
194
+ node scripts/capture-preflight.mjs capture.json
195
+
196
+ # 앱에 설치한 패키지에서 사용: 새 기능이 포함된 버전을 설치한 뒤
197
+ node node_modules/isthmus-cli/scripts/capture-preflight.mjs capture.json
198
+ ```
199
+
200
+ 아래는 iOS 앱용 설정 형태다. project·scheme·도구 경로와 입력 목록은 실제 앱에 맞춰야 한다.
201
+ 이 iOS 설정 자체를 실행 검증했다고 주장하지 않는다. 실검증한 SwiftPM fixture 절차는 아래에 있다.
202
+
203
+ ```json
204
+ {
205
+ "project": "/absolute/path/to/flutter-app",
206
+ "inputs": [
207
+ "lib", "ios/Runner", "ios/Runner.xcodeproj/project.pbxproj",
208
+ "ios/Podfile", "ios/Podfile.lock", "pubspec.yaml", "pubspec.lock",
209
+ ".dart_tool/package_config.json"
210
+ ],
211
+ "toolInputs": ["/absolute/path/to/cartograph", "/absolute/path/to/dartograph"],
212
+ "dartograph": ["/absolute/path/to/dartograph"],
213
+ "cartograph": ["/absolute/path/to/cartograph"],
214
+ "prepare": [[
215
+ "xcodebuild", "-workspace", "ios/Runner.xcworkspace", "-scheme", "Runner",
216
+ "-configuration", "Debug", "-sdk", "iphonesimulator",
217
+ "-derivedDataPath", ".isthmus/DerivedData",
218
+ "COMPILER_INDEX_STORE_ENABLE=YES", "CODE_SIGNING_ALLOWED=NO", "build"
219
+ ]],
220
+ "indexStore": ".isthmus/DerivedData/Index.noindex/DataStore",
221
+ "since": "main",
222
+ "output": ".isthmus/context.json",
223
+ "cache": ".isthmus/cache.json"
224
+ }
225
+ ```
226
+
227
+ `since` 대신 `selection: {"swift":{"files":["ios/Runner/CameraPlugin.swift"],"symbols":[]}}`
228
+ 처럼 변경 전 대상을 명시할 수 있다. 둘을 함께 쓰지 않는다. CI에서는 checkout한 저장소의
229
+ base commit SHA를 since에 넣고 해당 commit을 fetch해 둔다. Git의 rename 양쪽·삭제·변경
230
+ 파일과 미추적 Dart/Swift 소스를 선택한다. 추적된 설정·리소스 변경은 별도 검토 공백으로 남긴다.
231
+
232
+ Flutter 의존성을 준비한 후 수집기를 실행하고 생성된 JSON을 `isthmus preflight`에 전달한다.
233
+ 수집기 코드 1은 보고서가 생성됐지만 검토가 필요하다는 뜻이고, 2는 수집 실패다. CI 스크립트는
234
+ 1일 때도 context 보고서를 보존해야 한다. 외부로 올릴 artifact에 개인 경로가 있는지 검토한다.
235
+ `.isthmus/`와 빌드 출력은 Git에서 제외하고 입력 해시 범위에도 넣지 않는다.
236
+
237
+ 캐시는 **명시한 입력 범위**에서만 유효하다. 사용하는 xcconfig·Pigeon 입력·로컬 패키지·
238
+ SDK 버전 파일·producer가 로드하는 라이브러리와 설정도 inputs/toolInputs에 포함해야 한다.
239
+ 실행 파일이 wrapper이면 wrapper 하나의 해시만으로 실제 구현 전체를 보증하지 못한다.
240
+ 입력 디렉터리 내부 symlink는 거부하므로 필요한 실제 소스 경로를 별도로 선언한다.
241
+
242
+ 소스·설정·producer 파일 내용, 명령·선택 설정, isthmus 구현, Node/OS/아키텍처 및 주요
243
+ toolchain 환경의 해시가 키에 포함된다. 파일 추가·수정·삭제로 키가 바뀌면 prepare부터 다시
244
+ 실행한다. 수집 전후 해시가 다르면 결과를 발행하지 않는다. prepare가 lock 파일 등을 정상적으로
245
+ 갱신한 경우에는 준비가 끝난 상태부터 수집 전후를 비교한다. 빌드 명령이 실제 index를 갱신하는지는
246
+ 호출자가 책임지는 명시적 전제다. 코드 0을 임의의 빌드 설정까지 정확하다는 보증으로 삼지 않는다.
247
+
248
+ context 옆의 `.sources.json`에는 원래 producer 출력, 선언한 입력의 해시, 단계별 시간이 있다.
249
+ 캐시 복원 시에도 이 근거를 복원한다. 일반 호출 그래프로 투영하지 못한 Cartograph runtime
250
+ review·runtime dependency 항목은 공백으로 표시하고 원문 producer 보고서에서 확인한다.
251
+ 런타임 통신은 [별도 검증](RUNTIME.md)을 사용하며 이 정적 경로에 실제 실행 신원을 추측해 붙이지 않는다.
252
+
253
+ Basic 수집은 설정에 `"messages": true`를 추가한다. 같은 producer에 `bridges --messages`를
254
+ 호출하며 그 기능이 있는 개발 버전이 필요하다. 별도의 message producer를 쓸 때는
255
+ `"messages": {"cartograph": ["/path/to/message-cartograph"], "dartograph": ["/path/to/message-dartograph"]}`처럼
256
+ 명령을 지정한다. 생략한 쪽은 기본 producer를 사용한다. override 실행 파일과 관련 구현도
257
+ `toolInputs`에 넣어야 하며 feature 설정·명령·override 버전은 캐시 키에 포함된다.
258
+
259
+ ## 실행 근거
260
+
261
+ 현재 개발 producer가 있는 환경에서 다음 검증을 실행했다.
262
+
263
+ ```bash
264
+ node scripts/verify-preflight-producers.mjs /path/to/cartograph /path/to/dartograph/bin/dartograph.dart /path/to/flutter
265
+ ```
266
+
267
+ 임시 SwiftPM 소스의 실제 compiler index와 실제 Dartograph 분석을 사용해 helper→handler→
268
+ channel→Dart caller→service→screen 경로, source 위치, 캐시 재사용, 소스 변경 후 재수집을 확인한다.
269
+ Swift의 Flutter 타입은 컴파일용 stub이므로 native IPC 검증이 아니다. Flutter 의존성 준비는
270
+ offline 캐시를 사용한다. SDK·producer 저장소를 변경하지 않고 임시 fixture에서 빌드한다.
271
+
272
+ 2026-09-14 최신 실행은 첫 수집 23.811초, 같은 입력의 캐시 사용 3.735초, 소스 변경 후 18.458초였다.
273
+ Dartograph 소스를 직접 실행하므로 버전 조회에도 약 4초가 들었다. producer 설치·Flutter 의존성
274
+ 준비는 이 시간 밖이며, 대형 앱이나 배포된 producer 실행 파일의 성능으로 일반화하지 않는다.
275
+ 수집기의 코드·도구 변경/삭제·중간 변경·캐시 변조·다른 파일의 query·Git rename 회귀는
276
+ `node --test scripts/capture-preflight.test.mjs`로 검사하며 `npm run verify`에도 포함된다.
277
+
278
+ 소비자 성능 회귀는 `node scripts/benchmark-preflight.mjs`로 검사하며 저장소 CI에서도 실행한다.
279
+ CLI 시작·JSON 읽기·분석·직렬화를 포함한 5회 실행을 측정한다. 전이/runtime 대형 입력과
280
+ 같은 setup을 공유하는 Basic handler 10,000개의 단일 구현 선택을 포함한다. 각 입력의
281
+ 최대 실행 5초를 게이트로 쓰며 producer·인덱스·앱 빌드 시간은 이 게이트 밖이다.
282
+
283
+ 공개 source의 메서드별 전파 검증은 아래 명령으로 재현한다. 준비된 Flutter pub 캐시의
284
+ `url_launcher_macos 3.2.2` 원본과 실제 FlutterMacOS framework를 사용한다. 생성 파일 원본을
285
+ 수정하지 않고 검증용 SwiftPM manifest와 Dart dev dependency 제외를 적용한다.
286
+
287
+ ```bash
288
+ node scripts/verify-public-pigeon.mjs <flutter> <cartograph-impact> <cartograph-messages> <dartograph-entry-or-exe> <url_launcher_macos-3.2.2>
289
+ ```
290
+
291
+ Swift launch/canLaunch 구현을 각각 선택하면 해당 Dart wrapper만, 공통 setup을 선택하면
292
+ 양쪽 wrapper 모두를 포함해야 한다. 이 검증은 native IPC 실행과 별도다. 실제 macOS 통합은
293
+ `verify-flutter-runtime.mjs`의 기존 producer 인자 뒤에 `--message-cartograph <path>`를 추가한다.
294
+ 하네스는 공개 plugin을 임시 앱의 vendor로 복사하고 원본 bytes·명시된 입력 해시·동일 revision의
295
+ 정적 후보/runtime 관찰을 검증한다. 기본 Flutter recorder는 payload나 원문 오류를 기록하지 않는다.
@@ -0,0 +1,172 @@
1
+ # 런타임 통신 검증 계약
2
+
3
+ 개발 소스의 `verify-runtime`은 명시한 시나리오에서 실제 관찰한 통신 결과를 검증한다.
4
+ 아직 npm 0.5.0에는 없다. [Flutter 수집기](../packages/isthmus_runtime/README.md)는 앱의
5
+ BinaryMessenger에 주입해 실제 outgoing 호출을 기록한다. 정적 후보 연결은
6
+ [impact의 런타임 입력](IMPACT.md#런타임-관찰-연결)과
7
+ [preflight의 실행 대조](PREFLIGHT.md#전이-분석과-runtime-대조)를 사용한다.
8
+
9
+ 아래 fixtures/runtime JSON은 합성 소비자 검증이다. 실제 native 검증은 별도 절차로 구분한다.
10
+
11
+ ```bash
12
+ npm run build
13
+ node dist/cli/main.js verify-runtime \
14
+ --expectations fixtures/runtime/expectations.json fixtures/runtime/success.json --strict
15
+ ```
16
+
17
+ ## 실제 Flutter 앱 검증
18
+
19
+ ```bash
20
+ node scripts/verify-flutter-runtime.mjs /path/to/flutter/bin/flutter
21
+ # 실제 producer와 전이 분석까지 연결할 때(최신 isthmus build 필요)
22
+ node scripts/verify-flutter-runtime.mjs /path/to/flutter/bin/flutter dist/cli/main.js /path/to/cartograph /path/to/dartograph/bin/dartograph.dart
23
+ ```
24
+
25
+ macOS·Xcode·CocoaPods·Flutter SDK가 필요하며 최초 준비에는 네트워크를 사용한다. 스크립트는 임시 앱을
26
+ 만들고 `url_launcher_macos 3.2.2`를 고정해 실제 Pigeon 생성 API를 호출한다. URL을 열지 않고
27
+ `canLaunchUrl`만 실행한다. 자체 Swift MethodChannel/BasicMessageChannel도 함께 검사한다.
28
+ 공개 의존성은 pub.dev에서 해결한다. 앱·Pods 배포 대상은 macOS 12이며 시스템/사용자 앱 설정은 수정하지 않는다.
29
+
30
+ 성공 4개 기대(동적 호출·전이 소비자 호출·자체 Basic·공개 Pigeon), 네이티브 error·missing-handler·timeout의 구분, 앱 Future의 원래 지연 응답,
31
+ pending 실행의 미완료와 payload 미기록을 확인한다. 소스·의존성 잠금·SDK revision으로
32
+ 검증 revision을 만들며, 임시 앱은 정리하고 기록 JSON과 검증 요약 디렉터리를 출력한다.
33
+ 이 검사는 실제 macOS native 통신이며 iOS/Android 앱 검증으로 확대 해석하지 않는다.
34
+
35
+ producer 인자를 주면 실제 Xcode compiler index와 양쪽 producer로 별도 Swift helper에서
36
+ Dart runtimeBridge→runtimeService→runtimeScreen까지의 경로를 확인한다. context의 입력
37
+ 해시를 앱의 recorder revision으로 전달하고 같은 revision의 runtime과 preflight를 대조한다.
38
+ 동적 echo와 전이 소비자의 echo는 별도 시나리오·recorder로 기록해 다른 호출 위치를 공유하지 않는다.
39
+ Basic 정적 미지원·실행하지 않은 경계 때문에 preflight strict가 1인 경우도 근거로 보존한다.
40
+ 선택적 마지막 인자로 준비된 dartograph 실행 파일을 주면 소스 실행의 반복 JIT 비용을 줄일 수 있다.
41
+
42
+ 검증 SDK는 Flutter 3.32.2/Dart 3.8.1이다. 최초 SDK/공개 패키지 준비 시간과 소비자 CLI
43
+ 시간은 분리한다. 같은 앱의 첫 빌드와 변경 없는 재빌드를 둘 다 측정해 `steps`에 남긴다.
44
+
45
+ 수집기 자체의 검사와 고정 payload 오버헤드 측정:
46
+
47
+ ```bash
48
+ cd packages/isthmus_runtime
49
+ flutter pub get
50
+ flutter analyze --no-pub
51
+ flutter test --no-pub
52
+ flutter test tool/emit_runtime_fixture.dart --no-pub
53
+ flutter test tool/benchmark_recorder.dart --no-pub
54
+ ```
55
+
56
+ 벤치마크는 fake messenger를 쓰는 디버그 Flutter 테스트다. 실제 디바이스 IPC 지연의 측정이
57
+ 아니다. 임시/진단 빌드에서 필요한 채널과 실제 codec을 명시하며, Basic/Pigeon reply의 성공·
58
+ 실패 의미는 명시 classifier가 정한다. 대응하는 codec이 없으면 이름만으로 추측하지 않는다.
59
+
60
+ ## 실제 Android 앱 검증
61
+
62
+ 개발 소스의 Android 하네스는 별도 debug 앱에서 Kotlin MethodChannel/BasicMessageChannel
63
+ 핸들러를 실행하고 수집기 JSON을 검증한다. Android SDK·JDK·Flutter와 실행 가능한 Android
64
+ 장치 또는 에뮬레이터 이미지가 필요하다.
65
+
66
+ ```bash
67
+ node scripts/verify-flutter-android-runtime.mjs <flutter> <adb> <isthmus-js>
68
+ # 같은 capture의 Kotlin 정적 영향과 연결
69
+ node scripts/verify-flutter-android-runtime.mjs <flutter> <adb> <isthmus-js> \
70
+ --kartograph <kartograph-bin> --dartograph <dartograph-aot>
71
+ ```
72
+
73
+ API 36 arm64 실행에서는 자체 Method/Basic과 공개 `shared_preferences_android 2.4.1`의
74
+ Pigeon `getBool` 성공 3개, 기대한 오류·핸들러 누락·timeout 3개, pending 미완료와 자체
75
+ Kotlin 본문의 실행 marker를 확인했다. 공개 패키지의 원본 소스·LICENSE·실제 생성 codec을
76
+ 사용한다. 하네스가 만든 앱과 에뮬레이터만 정리하고 기록과 실행 인자는 근거 폴더에 남긴다.
77
+ 기본 공개 패키지 경로는 pub 캐시이며 `ISTHMUS_SHARED_PREFERENCES_ANDROID`로 지정할 수 있다.
78
+ 장치가 없으면 API 36 Google Play 이미지를 사용하며 `ISTHMUS_ANDROID_SYSTEM_IMAGE`로
79
+ 설치된 이미지 ID를 지정할 수 있다. 최신 조합의 결과는 [진행 기록](COMPETITIVENESS.md)에 남긴다.
80
+ 이는 명시한 통신 시나리오의 검증이며 임의의 런타임 의존성을 자동 발견하는 기능은 아니다.
81
+ 정적 연결에는 실제 앱 project와 capture revision을 사용하며 실행 후 식별자를 고쳐 맞추지 않는다.
82
+ 공개 Kotlin `SharedPreferencesPlugin.getBool`의 실제 snapshot ID만 변경 대상으로 선택하는
83
+ 별도 capture도 검사한다. 생성 Dart API와 앱 호출자까지 도달해야 통과하며, 이 선택의
84
+ revision은 원래 실행 기록과 구분한다. Kotlin의 공통 Pigeon 등록 함수는 여러 채널을 영향
85
+ 후보로 넓힐 수 있으므로 메서드별 완전한 정밀도를 주장하지 않는다.
86
+
87
+ ## 독립적인 기대 목록
88
+
89
+ `bridge-expectations` v1:
90
+
91
+ ```json
92
+ {
93
+ "format": "bridge-expectations",
94
+ "version": 1,
95
+ "project": "/app",
96
+ "revision": "tested-source-revision",
97
+ "checks": [{
98
+ "id": "photo-main-ios",
99
+ "scenario": "take-photo",
100
+ "platform": "ios",
101
+ "instance": "main",
102
+ "transport": "method-channel",
103
+ "channel": "example/camera",
104
+ "method": "takePhoto",
105
+ "allowedOutcomes": ["success"]
106
+ }]
107
+ }
108
+ ```
109
+
110
+ 실행 로그를 보고 기대 목록을 역생성하면 누락된 실행을 발견할 수 없다. 검증할 기능에서
111
+ 목록을 먼저 정한다. checks 1~10,000개, 고유한 id가 필요하다. 인스턴스 생략은 어떤
112
+ 인스턴스든 허용한다는 뜻이다. 엔진별 검증이 필요하면 인스턴스를 명시한다.
113
+ `allowedOutcomes`는 선택 사항이며 `success`, `error`, `missing-handler`, `timeout` 중
114
+ 1~4개의 중복 없는 terminal outcome만 허용한다. 생략하면 `success`만 허용하고,
115
+ `pending`은 응답 대기 상태라 기대 결과로 지정할 수 없다.
116
+
117
+ Flutter recorder에서 timeout은 관찰 제한시간을 넘겼다는 뜻이다. 늦은 응답이나 예외는 앱에
118
+ 그대로 전달하고 기록의 timeout은 유지한다. 실제 Future가 모두 끝난 뒤 finish하면 completed가
119
+ 될 수 있지만, 응답이 아직 남은 상태로 finish하면 incomplete다. 따라서 명시한 timeout 기대가
120
+ 아직 진행 중인 통신까지 허용하지 않는다. finish 후 기록은 나중 응답으로 변경되지 않는다.
121
+
122
+ ## 실행 기록
123
+
124
+ `bridge-runtime` v1은 같은 project·revision과 producer의 `tool: {name, version}`,
125
+ `run: {id, scenario, platform, status, startedAt, finishedAt?}`, `droppedEvents`, `events`를 가진다.
126
+
127
+ - platform은 `ios/macOS`를 추측하지 않고 `ios|macos|android|linux|windows` 중 하나로 선언한다.
128
+ - run status는 `completed|incomplete`. 완료 실행은 종료 시각이 필요하며 시작보다 앞설 수 없다.
129
+ - 시각은 bridge-facts와 같은 유효한 timezone 포함 ISO 형식이다.
130
+ - event는 `sequence`, `instance`, `transport`, `channel`, `method?`, `outcome`, `caller?`다.
131
+ sequence는 1부터 시작하는 고유한 정수, caller는 상대 `path`와 1부터 시작하는 `line/column`이다.
132
+ - transport `method-channel`은 method가 필요하다. `basic-message-channel`은 채널이 주소이며
133
+ method를 붙일 수 없다. Pigeon 메서드 이름이나 접미사를 채널 문자열에서 임의로 파싱하지 않는다.
134
+ - outcome은 `success|missing-handler|error|timeout|pending`. 인자·반환값·원문 오류·스택은
135
+ 출력 계약에 없으며 입력에 있더라도 파싱 단계에서 버린다. 수집기 자체도 저장하지 않아야 한다.
136
+ - 이벤트 100,000개 상한. 수집을 제한하면 `droppedEvents`를 증가시킨다. sequence 빈 구간은
137
+ 적어도 그만큼의 유실 계수가 있어야 한다. 응답 대기는 pending으로 남긴다.
138
+ - 입력 파일당 16×1024², 전체 64×1024² UTF-16 코드 유닛 예산. 실행 파일은 최대 256개다.
139
+ - run id는 전체 입력에서 유일해야 한다. 다른 project·중복 run은 계약 오류다.
140
+
141
+ project와 revision은 생산자의 선언이다. 소비자는 서로 일치하는지 검사하며 실제 checkout·
142
+ 빌드 내용까지 증명하지 않는다. 수집·CI 단계에서 변경된 소스의 정확한 식별자를 넣어야 한다.
143
+
144
+ ## 판정과 증거
145
+
146
+ 현재 revision의 run만 기대와 대조한다. 오래된 run은 `staleRuns`로 세고 전체 결과를 incomplete로
147
+ 만든다. 시나리오·플랫폼·transport·channel·method·선택한 instance가 모두 맞아야 관찰 근거다.
148
+
149
+ - `passed`: 해당 라우팅의 관찰이 하나 이상 있고 모든 terminal outcome이 기대의
150
+ `allowedOutcomes`에 포함되며 실행 중단·유실·대기가 없다.
151
+ - `failed`: 같은 호출에 기대하지 않은 terminal outcome이 하나라도 있다. 기대한
152
+ `error`·`missing-handler`·`timeout`도 원본 `failures`와 `failedCalls`에는 남기며,
153
+ `expectedFailedCalls`와 `unexpectedFailedCalls`로 구분한다. 기대 목록 밖의 실패는
154
+ `unexpectedFailedCalls`로 전체 상태를 실패로 만든다.
155
+ - `unobserved`: 해당 기대와 맞는 호출을 관찰하지 못했다.
156
+ - `incomplete`: 맞는 관찰이 있지만 실행 중단·유실·대기가 남았다.
157
+
158
+ 라우팅과 인스턴스가 겹치는 기대가 있으면 이벤트는 적용 가능한 모든 기대의
159
+ `allowedOutcomes`를 만족해야 한다. 넓은 인스턴스 기대가 특정 인스턴스 기대의 실패를
160
+ 가리지 않는다. 기대하지 않은 성공도 해당 check를 `failed`로 만들며 terminal 실패가
161
+ 없어도 전체 상태에 반영한다.
162
+
163
+ 기대 밖 통신 실패도 `failures`와 전체 status에 반영한다. 전체 결과는 실행 공백이나 미충족
164
+ 기대가 있으면 incomplete다. `scope: declared-scenarios`, `complete: false`를 항상 보존하므로
165
+ passed는 선언한 기대의 통과이며 모든 의존성·기능의 완전성 보증이 아니다.
166
+
167
+ 항목별 evidence는 runId·sequence·instance·outcome·caller로 원본 기록을 가리킨다. 최대 20개를
168
+ 표시하고 `observedCalls`·`evidenceOmitted`로 전체 수와 생략 수를 구분한다. 평가는 모든 호출에
169
+ 적용한다. 증거 표시 생략은 원본 수집 유실(`droppedEvents`)과 다르다.
170
+
171
+ 기본 모드는 보고서 생성 성공 0, strict는 failed/incomplete 1, 입력 실패 2, 사용법 오류 64다.
172
+ `--compact`는 JSON 공백만 줄인다. 로그에 비밀을 넣지 않는 책임은 수집기와 실행 환경에도 있다.