@morit/cli 1.3.0 → 1.4.2
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.
- package/README.md +3 -5
- package/assets/plugin_contract.json +33 -8
- package/bin/morit.js +0 -0
- package/package.json +1 -1
- package/src/cli.js +5 -0
- package/src/preview.js +254 -17
- package/src/workspace.js +218 -19
- package/assets/docs/README.md +0 -107
- package/assets/docs/ai-response-and-timeline.md +0 -203
- package/assets/docs/ai-skill-and-docx-workflow.md +0 -83
- package/assets/docs/app-builder.md +0 -56
- package/assets/docs/authentication.md +0 -140
- package/assets/docs/components.md +0 -216
- package/assets/docs/design-tokens-responsive.md +0 -171
- package/assets/docs/docs-index.json +0 -94
- package/assets/docs/examples-notion.md +0 -83
- package/assets/docs/examples-school-life.md +0 -79
- package/assets/docs/getting-started.md +0 -132
- package/assets/docs/information-hierarchy.md +0 -81
- package/assets/docs/instances-and-connectors.md +0 -93
- package/assets/docs/lifecycle-and-api.md +0 -169
- package/assets/docs/local-cli.md +0 -125
- package/assets/docs/manifest.md +0 -234
- package/assets/docs/packaging-and-testing.md +0 -121
- package/assets/docs/permissions-and-data.md +0 -149
- package/assets/docs/platform-compatibility.md +0 -62
- package/assets/docs/plugin-storage.md +0 -175
- package/assets/docs/project-structure.md +0 -102
- package/assets/docs/remote-mcp.md +0 -158
- package/assets/docs/school-life-privacy.md +0 -55
- package/assets/docs/screens-layout-navigation.md +0 -95
- package/assets/docs/sdk-and-mcp.md +0 -199
- package/assets/docs/tool-and-skill.md +0 -172
- package/assets/docs/troubleshooting.md +0 -121
- package/assets/docs/ui-extensions.md +0 -75
- package/assets/docs/ui-runtime-v2.md +0 -343
- package/assets/docs/verification.md +0 -133
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
# UI Runtime v2 레퍼런스
|
|
2
|
-
|
|
3
|
-
UI Runtime v2는 서명된 `.mplg`가 화면, 상태, data source, action을 JSON으로 선언하는 Host-rendered
|
|
4
|
-
계약입니다. Flutter/Dart, JavaScript, HTML, CSS, WebView, native code를 UI로 실행하지 않습니다.
|
|
5
|
-
|
|
6
|
-
## Config 구조
|
|
7
|
-
|
|
8
|
-
```json
|
|
9
|
-
{
|
|
10
|
-
"ui_schema": 2,
|
|
11
|
-
"icon": "calendar",
|
|
12
|
-
"description": "오늘과 이번 주 학교 생활을 확인합니다.",
|
|
13
|
-
"theme": {
|
|
14
|
-
"density": "standard",
|
|
15
|
-
"radius": 18,
|
|
16
|
-
"spacing": 12
|
|
17
|
-
},
|
|
18
|
-
"app_bar": {
|
|
19
|
-
"title": "학교 생활",
|
|
20
|
-
"subtitle": "{{data.today.summary}}",
|
|
21
|
-
"pinned": true,
|
|
22
|
-
"actions": [
|
|
23
|
-
{
|
|
24
|
-
"id": "settings",
|
|
25
|
-
"label": "설정",
|
|
26
|
-
"icon": "settings",
|
|
27
|
-
"show_as": "overflow",
|
|
28
|
-
"action": {"type": "navigate", "target": "school_life.settings"}
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
},
|
|
32
|
-
"navigation": {
|
|
33
|
-
"type": "adaptive",
|
|
34
|
-
"selected_state_key": "section",
|
|
35
|
-
"persist": true,
|
|
36
|
-
"label_behavior": "selected",
|
|
37
|
-
"items": [
|
|
38
|
-
{
|
|
39
|
-
"id": "today",
|
|
40
|
-
"label": "오늘",
|
|
41
|
-
"icon": "calendar",
|
|
42
|
-
"value": "today",
|
|
43
|
-
"action": {"type": "set_state", "values": {"section": "today"}}
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"id": "week",
|
|
47
|
-
"label": "이번 주",
|
|
48
|
-
"icon": "clock",
|
|
49
|
-
"value": "week",
|
|
50
|
-
"action": {"type": "navigate", "target": "school_life.week"}
|
|
51
|
-
}
|
|
52
|
-
]
|
|
53
|
-
},
|
|
54
|
-
"initial_state": {"section": "today"},
|
|
55
|
-
"data_sources": [
|
|
56
|
-
{
|
|
57
|
-
"id": "today",
|
|
58
|
-
"capability": "school_life.today",
|
|
59
|
-
"trigger": "load",
|
|
60
|
-
"query": null,
|
|
61
|
-
"arguments": {},
|
|
62
|
-
"refresh_seconds": 300
|
|
63
|
-
}
|
|
64
|
-
],
|
|
65
|
-
"view": {
|
|
66
|
-
"type": "column",
|
|
67
|
-
"props": {"spacing": 12},
|
|
68
|
-
"children": [
|
|
69
|
-
{"type": "text", "props": {"text": "{{data.today.summary}}", "style": "heading"}}
|
|
70
|
-
]
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
허용 config 필드는 `ui_schema`, `icon`, `description`, `placement`, `app_bar`, `navigation`,
|
|
76
|
-
`theme`, `initial_state`, `data_sources`, `view`입니다.
|
|
77
|
-
|
|
78
|
-
## 트리와 크기 제한
|
|
79
|
-
|
|
80
|
-
- tree 최대 160 node, depth 12(root가 depth 0), node당 child 최대 32개
|
|
81
|
-
- data source 최대 8개
|
|
82
|
-
- state key 최대 32개
|
|
83
|
-
- config JSON 최대 32 KiB
|
|
84
|
-
- 자동 refresh 30~86,400초
|
|
85
|
-
- 한 extension의 app bar action 최대 6개
|
|
86
|
-
- navigation item 2~8개
|
|
87
|
-
- binding 깊이 최대 12 segment
|
|
88
|
-
- JSON binding 값 깊이 최대 8, 배열·객체 항목 최대 64개
|
|
89
|
-
|
|
90
|
-
`positioned`, `scroll`, `padding`, `center`, `expanded`, `badge`는 child를 정확히 하나만 가집니다.
|
|
91
|
-
`surface`는 child가 하나 이상이어야 합니다. `positioned`는 `stack`의 직접 child이고 `left`, `top`,
|
|
92
|
-
`right`, `bottom` 중 하나 이상을 지정해야 합니다. `expanded`는 `row` 또는 `column`의 직접
|
|
93
|
-
child입니다.
|
|
94
|
-
|
|
95
|
-
크기 prop은 0~4096, position offset은 -4096~4096, spacing과 inset은 0~128입니다.
|
|
96
|
-
`border_width` 0~8, `border_radius` 0~64, `elevation` 0~24, `opacity` 0~1,
|
|
97
|
-
`aspect_ratio` 0.1~20, `flex` 1~24를 사용합니다. `padding`과 `margin`의 정확한 입력 형태와
|
|
98
|
-
theme 색상 역할은 [토큰과 반응형](design-tokens-responsive.md)에 있습니다.
|
|
99
|
-
|
|
100
|
-
## Plugin Theme
|
|
101
|
-
|
|
102
|
-
theme를 생략한 기존 플러그인은 Morit의 현재 Material 3 라이트·다크 theme를 그대로 상속합니다.
|
|
103
|
-
선택적으로 base와 mode별 일부 값만 재정의할 수 있고 누락 값은 항상 Host 값으로 채웁니다.
|
|
104
|
-
|
|
105
|
-
```json
|
|
106
|
-
{
|
|
107
|
-
"theme": {
|
|
108
|
-
"radius": 18,
|
|
109
|
-
"typography": {"scale": 1.0, "body_weight": 400, "title_weight": 700},
|
|
110
|
-
"surface": {"elevation": 1},
|
|
111
|
-
"border": {"width": 1, "radius": 18},
|
|
112
|
-
"icon": {"size": 22},
|
|
113
|
-
"states": {"disabled_opacity": 0.38},
|
|
114
|
-
"light": {
|
|
115
|
-
"color_scheme": {
|
|
116
|
-
"primary": "#315DA8",
|
|
117
|
-
"on_primary": "#FFFFFF",
|
|
118
|
-
"surface": "#F9F9FF",
|
|
119
|
-
"on_surface": "#1A1B20"
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
"dark": {
|
|
123
|
-
"color_scheme": {
|
|
124
|
-
"primary": "#AFC6FF",
|
|
125
|
-
"on_primary": "#002F66",
|
|
126
|
-
"surface": "#111318",
|
|
127
|
-
"on_surface": "#E2E2E9"
|
|
128
|
-
},
|
|
129
|
-
"states": {"selected_color": "primary_container"}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
지원 그룹은 `color_scheme`, `typography`, `surface`, `border`, `icon`, `states`, `radius`,
|
|
136
|
-
`spacing`, `density`와 최상위 `light`, `dark`입니다. mode variant 안에 다시 `light`/`dark`를 중첩할
|
|
137
|
-
수 없습니다. 명백히 같은 literal 전경/배경은 validate 오류이고, 계산 가능한 대비가 4.5:1보다
|
|
138
|
-
낮으면 경고합니다. semantic token이나 Host 상속 때문에 정적으로 확정할 수 없는 조합은 Preview와
|
|
139
|
-
실제 앱에서 확인합니다.
|
|
140
|
-
|
|
141
|
-
## State
|
|
142
|
-
|
|
143
|
-
`initial_state`가 허용 state key와 초기값을 선언합니다. key는 영문자로 시작하고 영문·숫자·밑줄을
|
|
144
|
-
사용하는 최대 64자입니다.
|
|
145
|
-
|
|
146
|
-
```json
|
|
147
|
-
{
|
|
148
|
-
"initial_state": {"period": "today", "notifications": true}
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
`field`, `select`, `switch`, navigation selection, `set_state`는 여기에 선언된 key만 변경합니다.
|
|
153
|
-
`persist: true`는 Instance settings의 extension 전용 namespace에 저장합니다. credential과 민감한
|
|
154
|
-
사용자 데이터는 state에 저장하지 않습니다.
|
|
155
|
-
|
|
156
|
-
## Data source와 binding
|
|
157
|
-
|
|
158
|
-
```json
|
|
159
|
-
{
|
|
160
|
-
"id": "schedule",
|
|
161
|
-
"capability": "school_life.schedule",
|
|
162
|
-
"trigger": "load",
|
|
163
|
-
"query": "{{state.period}}",
|
|
164
|
-
"arguments": {"period": "{{state.period}}"},
|
|
165
|
-
"refresh_seconds": 300
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
`trigger`는 `load` 또는 `manual`입니다. 결과는 다음 namespace로 읽습니다.
|
|
170
|
-
|
|
171
|
-
```text
|
|
172
|
-
data.<source_id>.completed
|
|
173
|
-
data.<source_id>.summary
|
|
174
|
-
data.<source_id>.data.<field>
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
반복 template 안에서는 `item.<field>`를 사용합니다. 문자열 전체가 binding 하나면 원래 JSON type을
|
|
178
|
-
arguments에 전달하고, 다른 글자와 섞으면 텍스트 template이 됩니다.
|
|
179
|
-
|
|
180
|
-
```json
|
|
181
|
-
{"limit": "{{state.limit}}"}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
```json
|
|
185
|
-
{"label": "{{state.period}} 일정"}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Local Storage binding
|
|
189
|
-
|
|
190
|
-
저장소도 같은 data source/action 흐름을 사용합니다.
|
|
191
|
-
|
|
192
|
-
```json
|
|
193
|
-
{
|
|
194
|
-
"id": "tasks",
|
|
195
|
-
"capability": "morit.storage.list",
|
|
196
|
-
"trigger": "load",
|
|
197
|
-
"query": "",
|
|
198
|
-
"arguments": {"namespace": "tasks", "limit": 20}
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
`list.source`에는 `data.tasks.data.items`를 사용합니다. Storage mutation이나 AI 응답 완료 뒤 Host가
|
|
203
|
-
Storage source만 갱신하며 앱 재개 시에도 최신 값을 읽습니다. CRUD, 권한과 migration은
|
|
204
|
-
[Plugin Local Storage와 AI 접근](plugin-storage.md)을 참고하세요.
|
|
205
|
-
|
|
206
|
-
### Capability 결과 이미지
|
|
207
|
-
|
|
208
|
-
`image`와 `avatar`의 `url`은 literal URL이 아니라 capability 결과를 가리키는 전체
|
|
209
|
-
`{{data.<source>...}}` 또는 `{{item...}}` binding입니다. Manifest가 `network`를 요청하고 활성
|
|
210
|
-
Instance가 `network`를 grant한 경우에만 표시할 수 있습니다.
|
|
211
|
-
|
|
212
|
-
Host renderer는 해석된 URL을 앱에서 직접 fetch하지 않고 인증된
|
|
213
|
-
`POST /v1/plugin-instances/{instance_id}/ui-images:fetch`를 사용합니다. 요청 body는 다음과 같습니다.
|
|
214
|
-
|
|
215
|
-
```json
|
|
216
|
-
{"url": "https://images.example.edu/today.png"}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Host proxy는 공개 HTTPS와 SSRF 정책을 적용하고 redirect를 따르지 않습니다. PNG/JPEG/GIF/WebP만
|
|
220
|
-
허용하며 응답 MIME type, magic byte, 실제 이미지 포맷이 모두 일치해야 합니다. 응답 본문은 최대
|
|
221
|
-
512 KiB, 가로·세로는 각각 최대 4096 px입니다. 정적 이미지는 1 frame, 애니메이션은 최대 128
|
|
222
|
-
frame이며 `width × height × frame 수`로 계산한 frame 합산 pixel은 최대 16,000,000입니다. 검증
|
|
223
|
-
실패는 해당 이미지 fallback으로 격리하며 원본 URL이나 실패 응답을 직접 렌더링하지 않습니다.
|
|
224
|
-
|
|
225
|
-
## App bar
|
|
226
|
-
|
|
227
|
-
`app_bar`는 Host 화면 chrome을 구성합니다. tree 안에 앱 바를 다시 만들지 않습니다.
|
|
228
|
-
|
|
229
|
-
| 필드 | 규칙 |
|
|
230
|
-
|---|---|
|
|
231
|
-
| `title` | 필수, binding 가능, 최대 120자 |
|
|
232
|
-
| `subtitle` | 선택, binding 가능, 최대 160자 |
|
|
233
|
-
| `center_title`, `pinned` | boolean |
|
|
234
|
-
| `leading` | action item 하나 |
|
|
235
|
-
| `actions` | 최대 6개 |
|
|
236
|
-
|
|
237
|
-
action item은 `id`, `label`, 선택적 `icon`, `show_as`, 필수 `action`을 가집니다. `show_as`는
|
|
238
|
-
`auto`, `always`, `overflow`입니다. overflow에는 드문 행동을 두고 주요 행동은 화면 본문에 둡니다.
|
|
239
|
-
|
|
240
|
-
## Navigation
|
|
241
|
-
|
|
242
|
-
`navigation.type`은 `tabs`, `bar`, `rail`, `drawer`, `adaptive`입니다. 각 item은 `id`, `label`,
|
|
243
|
-
선택적 `icon`, `selected_icon`, `value`, 필수 `action`을 가집니다.
|
|
244
|
-
|
|
245
|
-
`selected_state_key`를 사용하면 해당 key가 `initial_state`에 있어야 하고 모든 item에 `value`가
|
|
246
|
-
필요합니다. `label_behavior`는 `auto`, `always`, `selected`, `never`입니다. `rail_breakpoint`는
|
|
247
|
-
480~1600입니다.
|
|
248
|
-
|
|
249
|
-
## Component tree
|
|
250
|
-
|
|
251
|
-
지원 node:
|
|
252
|
-
|
|
253
|
-
```text
|
|
254
|
-
column row wrap grid stack positioned scroll padding center expanded
|
|
255
|
-
surface card section text icon image avatar badge divider spacer
|
|
256
|
-
button chip metric progress list timeline calendar chart
|
|
257
|
-
form dialog sheet field select switch empty
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
각 node의 목적과 props는 [컴포넌트 레퍼런스](components.md), 색·크기 범위는
|
|
261
|
-
[토큰과 반응형](design-tokens-responsive.md)을 참고하세요.
|
|
262
|
-
|
|
263
|
-
## Action
|
|
264
|
-
|
|
265
|
-
### Capability 실행
|
|
266
|
-
|
|
267
|
-
```json
|
|
268
|
-
{
|
|
269
|
-
"type": "invoke",
|
|
270
|
-
"capability": "school_life.lookup",
|
|
271
|
-
"query": "{{state.query}}",
|
|
272
|
-
"arguments": {"limit": 10},
|
|
273
|
-
"store": "results"
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
`store`는 선언된 data source ID이며 성공 결과로 해당 source를 교체합니다.
|
|
278
|
-
|
|
279
|
-
### State와 refresh
|
|
280
|
-
|
|
281
|
-
```json
|
|
282
|
-
{"type": "set_state", "values": {"period": "tomorrow"}, "persist": true}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
```json
|
|
286
|
-
{"type": "refresh", "source": "schedule"}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### Navigation
|
|
290
|
-
|
|
291
|
-
```json
|
|
292
|
-
{
|
|
293
|
-
"type": "navigate",
|
|
294
|
-
"target": "school_life.details",
|
|
295
|
-
"transition": "platform",
|
|
296
|
-
"replace": false
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
`transition`은 `platform`, `fade`, `slide`, `none`입니다. `replace: true`는 현재 route를 뒤로가기
|
|
301
|
-
stack에서 교체합니다. 같은 manifest의 UI extension ID만 target이 될 수 있습니다.
|
|
302
|
-
|
|
303
|
-
```json
|
|
304
|
-
{"type": "back"}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
## 조건부 표시
|
|
308
|
-
|
|
309
|
-
```json
|
|
310
|
-
{
|
|
311
|
-
"visible_when": {
|
|
312
|
-
"path": "data.schedule.data.items",
|
|
313
|
-
"exists": true
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
`equals`, `not_equals`, `exists` 중 정확히 하나를 사용합니다. 조건은 state·data·현재 `item` 값만
|
|
319
|
-
읽고 임의 expression을 실행하지 않습니다.
|
|
320
|
-
|
|
321
|
-
## 로딩과 오류 복구
|
|
322
|
-
|
|
323
|
-
Host는 source별 실행을 격리하고 중복 실행을 합칩니다. 화면이 background로 가면 자동 refresh를
|
|
324
|
-
멈추고 다시 foreground가 될 때 최신 source를 확인합니다. 갱신 실패 시 기존 정상 데이터가 있으면
|
|
325
|
-
유지하고, 첫 로드도 실패한 경우에만 해당 영역에 retry 상태를 표시합니다. package generation이나
|
|
326
|
-
화면이 바뀐 뒤 도착한 오래된 응답은 버립니다.
|
|
327
|
-
|
|
328
|
-
## Response UI
|
|
329
|
-
|
|
330
|
-
`point: "response"`도 같은 `theme`, `app_bar`, `navigation`, data/state/node/action 계약을
|
|
331
|
-
재사용합니다. 추가로 `a2ui.version`, 사용 조건을 설명하는 `a2ui.description`, 실제 Tool 결과를
|
|
332
|
-
검증할 `a2ui.schema`가 필수입니다. 결과는 AI가 catalog 후보를 명시적으로 선택하고 Host의
|
|
333
|
-
유효성·관련성·중복·권한 검증을 통과했을 때만 해당 AI 메시지의 한 response container 안에
|
|
334
|
-
렌더링됩니다. 서버는 response를 일괄 생성하거나 shape로 추측하지 않습니다. 상세 계약과 기본
|
|
335
|
-
catalog는 [Response UI와 Agent Timeline](ai-response-and-timeline.md)을 참고하세요.
|
|
336
|
-
|
|
337
|
-
## Preview와 실제 Host
|
|
338
|
-
|
|
339
|
-
`morit plugin preview . --output ./dist/preview.html` 또는 `morit_project_preview`는 validate를 먼저
|
|
340
|
-
통과한 config를 실행 코드 없이 안전한 HTML로 렌더링합니다. 상단 토글로 light/dark variant와 Host
|
|
341
|
-
상속 결과를 비교할 수 있습니다. 같은 manifest를 Flutter 앱이 실제 Material widget으로 렌더링하므로
|
|
342
|
-
node, binding, theme 계약은 공유하지만 focus, 키보드, route stack, 권한, 실제 Tool 호출은 서명
|
|
343
|
-
package를 앱에 설치해 최종 확인합니다.
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# 예제 검증 방법과 확인 경계
|
|
2
|
-
|
|
3
|
-
자동 테스트, 외부 서비스 live test, Android 실기기 확인은 서로 다른 증거입니다. 하나의 성공을
|
|
4
|
-
다른 단계의 성공으로 기록하지 않습니다. 실행 시각, 대상 version, 명령, 종료 코드, artifact
|
|
5
|
-
SHA-256을 함께 남기면 나중에 같은 결과를 재현할 수 있습니다.
|
|
6
|
-
|
|
7
|
-
## 1. 패키지 무결성
|
|
8
|
-
|
|
9
|
-
현재 저장소의 예제 artifact는 다음과 같습니다.
|
|
10
|
-
|
|
11
|
-
| 패키지 | 크기 | SHA-256 |
|
|
12
|
-
|---|---:|---|
|
|
13
|
-
| `school-life-1.5.0.mplg` | 263433 bytes | `0939f8956037638d56f7672ac4f9c4d8f6472d58d23840c1fd5baab198c41e29` |
|
|
14
|
-
| `notion-1.1.0.mplg` | 3044 bytes | `eba57c440b305343c5cf917259e30e50b322272cfadb0ed64d39ecfc47539c4e` |
|
|
15
|
-
|
|
16
|
-
```powershell
|
|
17
|
-
python tools/morit_plugin.py verify `
|
|
18
|
-
examples/plugins/school-life/dist/school-life-1.5.0.mplg
|
|
19
|
-
python tools/morit_plugin.py verify `
|
|
20
|
-
examples/plugins/notion/dist/notion-1.1.0.mplg
|
|
21
|
-
Get-FileHash -Algorithm SHA256 @(
|
|
22
|
-
'examples/plugins/school-life/dist/school-life-1.5.0.mplg',
|
|
23
|
-
'examples/plugins/notion/dist/notion-1.1.0.mplg'
|
|
24
|
-
)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
`verify`는 ZIP 안전성, canonical payload, embedded Ed25519 public key와 signature를 검사합니다.
|
|
28
|
-
검증 결과에 publisher key가 포함되어 있어야 하며, trusted publisher 사전 등록 여부와 무관하게
|
|
29
|
-
서명 자체가 유효해야 합니다. Host의 신뢰 정책은 설치·실행 단계에서 별도로 적용됩니다.
|
|
30
|
-
|
|
31
|
-
문서의 hash는 source나 build 규칙이 바뀌면 달라집니다. 새 artifact를 만들었으면 위 명령으로
|
|
32
|
-
실제 파일을 다시 계산한 뒤 문서와 배포 metadata를 함께 갱신합니다.
|
|
33
|
-
|
|
34
|
-
## 2. 자동 회귀 테스트
|
|
35
|
-
|
|
36
|
-
저장소 root에서 다음 검사를 먼저 실행합니다.
|
|
37
|
-
|
|
38
|
-
```powershell
|
|
39
|
-
npx -y @morit/cli plugin validate .\examples\plugins\school-life
|
|
40
|
-
npx -y @morit/cli plugin validate .\examples\plugins\notion
|
|
41
|
-
python -m unittest discover -s backend/archive_processing -p "test_*.py"
|
|
42
|
-
python -m unittest discover -s backend/plugin_mcp -p "test_*.py"
|
|
43
|
-
dart analyze
|
|
44
|
-
flutter test
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
관련 backend 테스트는 정상 package 설치·복원·삭제, actor 격리, permission/readiness gate,
|
|
48
|
-
Connection OAuth state와 replay 차단, credential 암호화·갱신·해제, host-action outbox,
|
|
49
|
-
Slash·Search·Tool routing, UI/Response 계약을 검사합니다. transport fixture 테스트는 외부 서비스가
|
|
50
|
-
응답하지 않아도 request/response parser 계약을 확인하지만 실제 사용자 계정의 성공을 증명하지는
|
|
51
|
-
않습니다.
|
|
52
|
-
|
|
53
|
-
## 3. Backend live E2E
|
|
54
|
-
|
|
55
|
-
`tools/plugin_live_e2e.py`는 임시 Supabase 사용자를 만들고 공개 API를 호출합니다. 관리자 key를
|
|
56
|
-
사용하므로 승인된 운영자 환경에서만 실행하며 Secret, OAuth code, access token을 로그에 남기지
|
|
57
|
-
않습니다.
|
|
58
|
-
|
|
59
|
-
```powershell
|
|
60
|
-
$env:MORIT_E2E_ENV = 'C:\secure\morit-backend.env'
|
|
61
|
-
$env:MORIT_E2E_API = 'https://morit-api.moring.co'
|
|
62
|
-
$env:MORIT_SCHOOL_PLUGIN = 'examples/plugins/school-life/dist/school-life-1.5.0.mplg'
|
|
63
|
-
$env:MORIT_NOTION_PLUGIN = 'examples/plugins/notion/dist/notion-1.1.0.mplg'
|
|
64
|
-
|
|
65
|
-
# 실제 Notion 승인을 할 때만 0보다 큰 값을 사용합니다.
|
|
66
|
-
$env:MORIT_E2E_WAIT_NOTION_OAUTH_SECONDS = '300'
|
|
67
|
-
$env:MORIT_E2E_NOTION_QUERY = 'Morit 프로젝트'
|
|
68
|
-
$env:MORIT_E2E_NOTION_PAGE_QUERY = 'Morit 프로젝트 안내'
|
|
69
|
-
$env:MORIT_E2E_NOTION_DATA_SOURCE_QUERY = 'Morit 작업 목록'
|
|
70
|
-
|
|
71
|
-
python tools/plugin_live_e2e.py
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
live E2E에서 확인할 핵심 순서는 다음과 같습니다.
|
|
75
|
-
|
|
76
|
-
1. 신규 사용자의 설치·Instance 목록이 비어 있고 traversal package가 부분 상태 없이 차단됨
|
|
77
|
-
2. 학교와 Notion package를 함께 설치하면 각 설치의 기본 Instance가 정확히 하나 생성됨
|
|
78
|
-
3. 권한 승인 전 capability가 노출되지 않고, 승인 후 학교 Tool·Search·Slash·background가 등록됨
|
|
79
|
-
4. 실제 NEIS 조회, 검색, 알림 outbox lease/ACK, 재시작 후 registry 복원이 동작함
|
|
80
|
-
5. Notion 승인 시 기본 Instance의 Connection이 활성화되고 실제 page/data source를 읽음
|
|
81
|
-
6. Connection 삭제 후 credential을 다시 쓰지 않으며 capability가 즉시 제거됨
|
|
82
|
-
7. disable/delete/purge 뒤 cache, 설정, credential, pending action이 남지 않음
|
|
83
|
-
|
|
84
|
-
같은 외부 서비스를 여러 계정으로 쓰는 경우 설치나 root Instance를 복제하지 않습니다. 하나의
|
|
85
|
-
기본 Instance 아래 여러 Connection을 만들고 `allow_multiple` 정책과 계정별 격리를 검사합니다.
|
|
86
|
-
|
|
87
|
-
스크립트 결과는 다음처럼 해석합니다.
|
|
88
|
-
|
|
89
|
-
| 출력 | 종료 코드 | 의미 |
|
|
90
|
-
|---|---:|---|
|
|
91
|
-
| `backend_live_plugin_e2e=passed` | 0 | 요청한 외부 단계까지 모두 확인 |
|
|
92
|
-
| `backend_live_plugin_e2e=partial reasons=...` | 2 | 일부 외부 승인·재시작 단계가 빠짐 |
|
|
93
|
-
| `backend_live_plugin_e2e=failed ...` | 1 | 구현·배포·API 검증 실패 |
|
|
94
|
-
|
|
95
|
-
Notion 사용자 승인을 생략했거나 운영 process 재시작 명령을 주지 않은 실행은 `partial`입니다.
|
|
96
|
-
`state=completed`나 검색 결과 수만으로 page/data source 본문 조회 성공을 추정하지 않습니다.
|
|
97
|
-
|
|
98
|
-
## 4. Android 실기기 확인
|
|
99
|
-
|
|
100
|
-
Backend live test는 앱 화면과 Android 알림을 누르지 않습니다. release APK를 실제 기기에 설치해
|
|
101
|
-
다음을 별도로 확인합니다.
|
|
102
|
-
|
|
103
|
-
1. `.mplg` 파일 선택 후 설치·권한·초기 설정 흐름
|
|
104
|
-
2. 학교 후보 선택, 오늘·주간·급식 화면과 빈 상태·오류 상태
|
|
105
|
-
3. Notion 공식 승인, Connection 추가·전환·해제와 실제 page/data source 표시
|
|
106
|
-
4. AI 자연어·Slash·Search와 해당 AI 메시지 안의 Response UI
|
|
107
|
-
5. 알림 권한 승인 후 실제 알림 표시
|
|
108
|
-
6. 강제 종료·재실행 뒤 설치·연결·화면 상태 복원
|
|
109
|
-
7. 비활성화·삭제 뒤 화면·capability·알림 제거
|
|
110
|
-
|
|
111
|
-
Response UI는 정상 데이터에서 필요한 컴포넌트 하나만 선택되는지뿐 아니라 빈 데이터, 관련 없는
|
|
112
|
-
질문, 같은 결과의 중복, schema 불일치, 권한 미승인, Tool 실패에서 컴포넌트가 생성되지 않고 일반
|
|
113
|
-
텍스트 답변이 유지되는지도 확인합니다. Plugin Response는 `plugin.<extension-id>` 등록 ID와 실제
|
|
114
|
-
실행 capability의 data source가 일치해야 합니다.
|
|
115
|
-
|
|
116
|
-
실기기에서 확인하지 않은 항목은 자동 테스트가 통과했더라도 `미검증`으로 남깁니다.
|
|
117
|
-
|
|
118
|
-
## 5. 결과 기록 형식
|
|
119
|
-
|
|
120
|
-
```text
|
|
121
|
-
시각(KST):
|
|
122
|
-
source revision:
|
|
123
|
-
Plugin/Host version:
|
|
124
|
-
실행 명령과 종료 코드:
|
|
125
|
-
artifact 경로·크기·SHA-256:
|
|
126
|
-
자동 테스트:
|
|
127
|
-
live E2E:
|
|
128
|
-
Android/iOS/Desktop 실기기:
|
|
129
|
-
생략 또는 실패한 단계와 이유:
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
성공 기록에는 실행하지 않은 항목을 넣지 않습니다. iOS·Desktop은 현재 Host 지원 범위를 먼저
|
|
133
|
-
확인하고, 지원이 확정되지 않은 플랫폼을 Android 결과로 대신하지 않습니다.
|