@h-ai/kit 0.1.0-alpha.67 → 0.1.0-alpha.69

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 (44) hide show
  1. package/README.en.md +67 -13
  2. package/README.ja.md +67 -13
  3. package/README.md +67 -13
  4. package/dist/adapter/index.d.ts +17 -2
  5. package/dist/adapter/index.js.map +1 -1
  6. package/dist/browser.d.ts +12 -6
  7. package/dist/browser.js +6 -7
  8. package/dist/browser.js.map +1 -1
  9. package/dist/{chunk-DVW7CQ3H.js → chunk-2PWGJCWX.js} +3 -3
  10. package/dist/chunk-2PWGJCWX.js.map +1 -0
  11. package/dist/chunk-OVRZIDCD.js +175 -0
  12. package/dist/chunk-OVRZIDCD.js.map +1 -0
  13. package/dist/{chunk-VOTDAVCB.js → chunk-QNLZLTVC.js} +29 -42
  14. package/dist/chunk-QNLZLTVC.js.map +1 -0
  15. package/dist/{chunk-MLSP67JF.js → chunk-UTCS6FR7.js} +67 -110
  16. package/dist/chunk-UTCS6FR7.js.map +1 -0
  17. package/dist/{chunk-NVYIUBU7.js → chunk-XC7I2L3G.js} +2 -2
  18. package/dist/chunk-XC7I2L3G.js.map +1 -0
  19. package/dist/{chunk-L7BTMAKX.js → chunk-Y2YUA3VR.js} +5 -6
  20. package/dist/chunk-Y2YUA3VR.js.map +1 -0
  21. package/dist/client/index.d.ts +47 -1
  22. package/dist/client/index.js +3 -3
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/index.d.ts +251 -347
  25. package/dist/index.js +347 -171
  26. package/dist/index.js.map +1 -1
  27. package/dist/kit-a2a-types-DwcmE668.d.ts +136 -0
  28. package/dist/{index-B7s-0mZl.d.ts → kit-client-auth-B5y1g5_A.d.ts} +228 -102
  29. package/dist/kit-crypto-types-Cwj9bprV.d.ts +196 -0
  30. package/dist/{kit-i18n-D9Qqk-1k.d.ts → kit-i18n-DEzmzE5T.d.ts} +142 -29
  31. package/dist/{kit-types-ByWdbCeO.d.ts → kit-types-DQYqP1Cn.d.ts} +242 -162
  32. package/dist/modules/a2a/index.d.ts +13 -111
  33. package/dist/modules/a2a/index.js +2 -2
  34. package/dist/modules/crypto/index.d.ts +28 -195
  35. package/dist/modules/crypto/index.js +3 -3
  36. package/dist/modules/crypto/index.js.map +1 -1
  37. package/package.json +16 -11
  38. package/dist/chunk-DVW7CQ3H.js.map +0 -1
  39. package/dist/chunk-L7BTMAKX.js.map +0 -1
  40. package/dist/chunk-MLSP67JF.js.map +0 -1
  41. package/dist/chunk-NVYIUBU7.js.map +0 -1
  42. package/dist/chunk-QHYNL24G.js +0 -117
  43. package/dist/chunk-QHYNL24G.js.map +0 -1
  44. package/dist/chunk-VOTDAVCB.js.map +0 -1
package/README.en.md CHANGED
@@ -22,6 +22,8 @@
22
22
 
23
23
  ## Getting started
24
24
 
25
+ `sequence` preserves SvelteKit resolve options: page transformations run from inner to outer, while the first preload and serialized-header filter win. This preserves the i18n HTML language replacement.
26
+
25
27
  ### Installation
26
28
 
27
29
  ```bash
@@ -51,7 +53,7 @@ const config = {
51
53
  kit: {
52
54
  adapter: adapter(),
53
55
  // 双构建模式:import { createAdapter } from '@h-ai/kit/adapter'
54
- // adapter: createAdapter(),
56
+ // adapter: await createAdapter(),
55
57
  },
56
58
  }
57
59
 
@@ -80,24 +82,28 @@ When using Crypto or Kit/IAM, include `sm-crypto@0.5.7` in application productio
80
82
 
81
83
  ```typescript
82
84
  import type { Handle } from '@sveltejs/kit'
85
+ import { cache } from '@h-ai/cache'
83
86
  import { kit } from '@h-ai/kit'
84
87
 
85
88
  const haiHandle = kit.createHandle({
86
89
  auth: {
87
90
  verifyToken: async (token) => {
88
- return token ? { userId: 'u_1', roles: ['admin'], permissions: ['user:read'] } : null
91
+ const verified = await iam.auth.verifyToken(token)
92
+ return verified.success ? verified.data : null
89
93
  },
90
94
  loginUrl: '/auth/login',
91
95
  protectedPaths: ['/admin/*', '/api/*'],
92
96
  publicPaths: ['/api/auth/*', '/api/public/*'],
93
97
  },
94
- rateLimit: { windowMs: 60_000, maxRequests: 100 },
98
+ rateLimit: { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' }) },
95
99
  logging: true,
96
100
  })
97
101
 
98
102
  export const handle: Handle = haiHandle
99
103
  ```
100
104
 
105
+ Rate limits use fixed windows beginning at the first successful commit; later requests preserve the TTL. A store is required. Initialize shared Redis and check its result before serving; all nodes use the same namespace/windowMs/maxRequests. Cache read/commit failure or exhausted contention returns 503, while exceeding the limit returns 429, without get+set or local fallback. createMemoryRateLimitStore is for development or explicit HAI_E2E/HAI_E2E_MOCK tests; it has no timer and can be garbage collected when released; each request scans at most 64 entries, capacity is 10000 windows, new keys receive 503 at capacity without evicting active counts, and external stores remain owned by their creators. Never enable test flags in production. Reset headers estimate node time; Redis TTL controls the actual window, so synchronize clocks. The default key uses the trusted adapter getClientAddress, normalizes equivalent IPv6/mapped IPv4 addresses and ignores raw forwarded headers; configure proxy trust in the deployment adapter.
106
+
101
107
  #### API endpoints
102
108
 
103
109
  ```typescript
@@ -136,21 +142,26 @@ const response = await apiFetch('/api/users', { method: 'GET' })
136
142
  ```
137
143
 
138
144
  > `apiFetch` injects the CSRF header automatically; `@h-ai/kit` does not expose a separate CSRF middleware factory.
145
+ > `apiFetch` accepts a string, URL, or Request. It preserves the Request method, headers, and body and adds CSRF for writes. With transport enabled, supported bodies are encrypted and multipart uploads retain their original boundaries.
139
146
  >
140
- > Authentication: prefer httpOnly cookies for same-origin SvelteKit endpoints and verify them on the server. Browsers need not write sensitive tokens to `localStorage`. If Bearer headers are required, provide a custom `BrowserTokenStore`, or set an in-memory token with `kit.auth.setBrowserToken()` before `kit.client.create({ auth: true })`.
147
+ > Use httpOnly cookies for same-origin SvelteKit endpoints and verify them on the server. For browser Bearer headers, set an in-memory token with `kit.auth.setBrowserToken()` before `kit.client.create({ auth: true })`. Custom `BrowserTokenStore` implementations must use memory or platform secure storage; never persist sensitive credentials in Web Storage.
141
148
 
142
149
  #### Same-origin transport encryption
143
150
 
144
- kit delegates transport encryption to `@h-ai/crypto`. The server creates a manager with `crypto.transport.createServer()`; the browser installs a same-origin fetch wrapper with one call to `kit.client.installBrowserTransport(kitConfig, { crypto })`. Applications only configure the top-level `crypto` handle.
151
+ kit delegates transport encryption to `@h-ai/crypto`. The server creates a manager with `crypto.transport.createServer({ serverKeyRing, keyStore })`; the browser installs a same-origin fetch wrapper with one call to `kit.client.installBrowserTransport(kitConfig, { crypto })`. Servers must also inject a managed key ring and shared registry.
145
152
 
146
153
  **src/hooks.server.ts**:
147
154
 
148
155
  ```ts
149
156
  import { cache } from '@h-ai/cache'
150
- import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
157
+ import { createRedisTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
151
158
  import { kit } from '@h-ai/kit'
152
159
 
153
160
  await crypto.init()
161
+ const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
162
+ if (!ring.success)
163
+ throw new Error(ring.error.message)
164
+ const serverKeyRing = ring.data
154
165
  await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
155
166
 
156
167
  export const handle = kit.createHandle({
@@ -162,7 +173,7 @@ export const handle = kit.createHandle({
162
173
  crypto: {
163
174
  crypto,
164
175
  transport: {
165
- requireEncryption: true,
176
+ serverKeyRing,
166
177
  keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
167
178
  },
168
179
  },
@@ -197,11 +208,17 @@ export const { apiFetch } = kit.client.create()
197
208
 
198
209
  The default key exchange path is `/api/_hai/key-exchange`. If the server customizes `transport.keyExchangePath`, update the client's `transport.keyExchangeUrl` accordingly.
199
210
 
200
- For shared client public keys across nodes, inject `createRedisTransportKeyStore()` or `createReldbTransportKeyStore()` through `kit.createHandle({ crypto: { transport: { keyStore } } })` at runtime. These object dependencies **must not** go in `_kit.yml`; configuration files contain only public paths and switches.
211
+ Servers must explicitly inject the same managed `serverKeyRing` and a shared `keyStore` (Redis or a shared database). Sharing client public keys alone cannot enable cross-node decryption. There is no implicit process key or memory-registry fallback; development/tests may explicitly use `createInMemoryKeyStore(capacity)`. Reload the same ring after restart; sticky sessions are unnecessary.
201
212
 
202
213
  Transport applies by default to same-origin `/api/*` endpoints, SvelteKit `__data.json` page data, and key exchange itself. Page documents and static assets remain plaintext and do not require `X-Client-Id`. `multipart/form-data` requests such as uploads are sent unchanged; explicitly allow their paths through server `excludePaths`.
203
214
 
204
- Security defaults to fail-closed: with `requireEncryption: true`, protected paths without `X-Client-Id` return 400. An unavailable transport manager, unencryptable response, or encryption size limit returns an error rather than forwarding plaintext business data. `requireEncryption: false` is for staged migration only and is not recommended in production.
215
+ Only authenticated protocol v2 is accepted. Independent random SM4-CBC and HMAC-SM3 keys are wrapped with SM2; verify the sender signature and MAC before CBC decryption. Clients sign requests and the selected server key version signs responses. Authentication covers version, direction, serverKeyId, clientId, a random 128-bit requestId acting as the nonce, issuedAt, HTTP method, the SM3 pathHash of pathname plus the original search, and response status. Requests have a five-minute lifetime and at most 30 seconds of future clock skew. The shared registry atomically consumes (clientId, requestId), retaining it until at least issuedAt plus six minutes to cover node skew; storage failures reject requests. Synchronize node clocks and use a Redis noeviction/durable policy that preserves live nonces. Transport replay prevention does not replace business idempotency keys.
216
+
217
+ Empty POSTs still send an authenticated envelope for empty plaintext. GET/HEAD carry it in X-Hai-Transport; bodyless responses such as 204/304/HEAD use the same header. Other requests/responses carry a JSON envelope and X-Encrypted: true. The client must match the original request context and HTTP status; missing markers, old formats and authentication failures reject. Exclude plaintext endpoints explicitly with excludePaths. CORS must allow and expose X-Hai-Transport. TLS authenticates handshake keys; this protocol does not replace IAM authorization.
218
+
219
+ Set `transport.maxPlaintextBytes` for both request and response JSON plaintext, measured in UTF-8 bytes: default 1 MiB, integer range 1 KiB–64 MiB. Clients learn the server limit during key exchange. `resolveTransportBodyLimits()` derives encrypted/proxy bytes as twice the plaintext limit plus 32 KiB. Handshake, public-key, header and replay constraints remain independent. Oversized streams are cancelled and readers released: requests return 413; oversized responses return an encrypted 500 capacity error, recognized as `TransportBodyTooLargeError` / `hai:crypto:070`. [Transport capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md)。
220
+
221
+ serv/kit return an encrypted HTTP 400 time error for an out-of-window message only after request binding, signature and MAC verification. The client throws `TransportTimeError` (`hai:crypto:071`) with advice to synchronize device/server clocks and refresh. `api-client` maps it to `VALIDATION_FAILED` and preserves `cause.transportCode`. Requests are not replayed automatically and replay windows remain unchanged; tampered or unauthenticated messages retain the generic authentication failure.
205
222
 
206
223
  #### Shared transport configuration with `_kit.yml`
207
224
 
@@ -215,13 +232,10 @@ server:
215
232
 
216
233
  transport:
217
234
  keyExchangePath: /api/_hai/key-exchange
218
- requireEncryption: true
219
- encryptResponse: true
220
235
  excludePaths:
221
236
  - /api/storage
222
237
  - /api/public
223
238
  - /api/auth/profile/avatar
224
- maxClients: 10000
225
239
  ```
226
240
 
227
241
  ```ts
@@ -253,23 +267,30 @@ Because the browser may read this configuration, `_kit.yml` must contain only pu
253
267
 
254
268
  With `crypto.encryptedCookies`, also provide `crypto.cookieEncryptionKey` or `HAI_KIT_COOKIE_KEY`. Missing keys fail immediately in `createHandle()`. Plaintext, damaged ciphertext, or decryption failures for protected cookies are treated as absent; encryption failures never fall back to writing plaintext.
255
269
 
270
+ Authentication cookies default to `Secure` when either `NODE_ENV` or `HAI_CORE_ENV` is `production`; `HAI_E2E` / `HAI_E2E_MOCK` do not relax cookie security. Production rate limiting recognizes both flags; only explicit test switches permit memory counters.
271
+
256
272
  ### Export paths
257
273
 
274
+ Use runtime methods through the `kit` namespace or explicit subpaths; types are exported separately. Browser declarations do not expose server factories. Vite configuration uses `@sveltejs/kit/vite`; there is no `@h-ai/kit/vite` plugin.
275
+
258
276
  - `@h-ai/kit`: main entry, with the `kit` namespace and types.
259
277
  - `@h-ai/kit/client`: browser client.
260
278
  - `@h-ai/kit/adapter`: dual build adapter (`createAdapter()`).
261
- - `@h-ai/kit/vite`: Vite plugin.
262
279
  - `@h-ai/kit/a2a`: A2A integration.
263
280
  - `@h-ai/kit/crypto`: crypto integration.
264
281
 
265
282
  ### Configuration driven authentication
266
283
 
284
+ Password login submits only `identifier` and `password`; `AuthPanel` maps the `LoginForm` field `username` to `identifier`. Server settings `session.maxAge` / `session.refreshTokenMaxAge` control session lifetimes. The login form has no per-login persistent-session option.
285
+
267
286
  Agreements support configuration-loaded userAgreementMarkdown/privacyPolicyMarkdown and URLs. Markdown takes priority for the same agreement and is safely rendered in a modal.
268
287
 
269
288
  Export standard handlers from kit.auth.createApi(iam), use kit.auth.publicPaths for anonymous endpoints, and connect AuthPanel with kit.client.createAuthActions({ apiFetch }). Validation, Cookie writes and HaiResult mapping are built in; current-user and password-change endpoints remain guarded.
270
289
 
271
290
  [完整 IAM 配置 / IAM configuration](../iam/README.en.md)
272
291
 
292
+ `kit.auth.logout(cookies, accessToken)` returns `Promise<HaiResult<void>>`. Without a token it only clears cookies and succeeds; with a token but no configured auth operations it fails. Local cookies are cleared for every server outcome. Callers must preserve failed results in their HTTP response or UI; local clearing does not confirm server revocation. Underlying control-flow exceptions still propagate.
293
+
273
294
  ### Error handling
274
295
 
275
296
  - Wrap API endpoints with `kit.handler()`.
@@ -288,12 +309,45 @@ Declarative CRUD fields support `colSpan: 'auto' | 1 | 2` for UI form widths. `v
288
309
 
289
310
  > Shared cross-platform HTTP contracts belong in `@h-ai/api-contract`, are mounted by `@h-ai/serv`, and are called through the `@h-ai/api-client` typed client. `@h-ai/kit` does not define or host business API contracts.
290
311
 
312
+ ### Request locale isolation
313
+
314
+ Wrap Node requests and SSR in `core.i18n.runWithLocale(locale, () => handleRequest())`. SvelteKit can use `kit.i18n.runWithLocale(locale, () => resolve(event))`; serv creates the scope automatically from request headers. Async descendants inherit it. Explicit `MessageOptions.locale` takes precedence over the request locale and the startup default. Within a scope, `setGlobalLocale` only changes that request; outside a scope it configures the startup default and must not be called by request hooks.
315
+
316
+ Browsers keep application-level `setGlobalLocale` / `createLocaleStore`. During SSR, UI store initialization and setters only update the store; messages read the request scope. `runWithLocale` requires the Node entry; browser translations use explicit locale options or application state.
317
+
291
318
  ## Development and testing
292
319
 
293
320
  ```bash
294
321
  pnpm --filter @h-ai/kit test
295
322
  ```
296
323
 
324
+ Kit has no module-wide init/close. The application initializes and closes shared cache, IAM and crypto dependencies. See the [JSDoc conventions (Chinese)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证) for typechecked public examples. Authenticate requests with iam.auth.verifyToken; a nonempty token is not proof of identity.
325
+
326
+ ## Feedback endpoint and request correlation
327
+
328
+ `kit.createFeedbackHandler` can serve GET/POST/PATCH in `/api/feedback/[...path]/+server.ts`. It reuses the same api-contract schemas as serv, bounds the input stream, and rejects client identity/scope fields. resolveContext maps an already verified locals.session to server-controlled scope and permissions; null denies access. Keep the existing createHandle authentication, CSRF, and transport encryption.
329
+
330
+ createHandle preserves a valid bounded incoming X-Request-Id, adds it to responses, and propagates the request and verified user across asynchronous work. To collect ordinary business logs, wrap the actual business operation in a host middleware using `core.context.run({ scopeId: 'configured-product' }, next)`. This trusted scope must match feedback scope and cannot come from client headers.
331
+
332
+ ```ts
333
+ import type { SessionData } from '@h-ai/kit'
334
+ import { feedback } from '@h-ai/feedback'
335
+ import { kit } from '@h-ai/kit'
336
+
337
+ const handler = kit.createFeedbackHandler({
338
+ feedback,
339
+ resolveContext: (event) => {
340
+ const session = (event.locals as { session?: SessionData }).session
341
+ return session
342
+ ? { scopeId: 'configured-product', userId: session.userId, permissions: session.permissions }
343
+ : null
344
+ },
345
+ })
346
+ export const GET = handler
347
+ export const POST = handler
348
+ export const PATCH = handler
349
+ ```
350
+
297
351
  ## License
298
352
 
299
353
  Apache-2.0
package/README.ja.md CHANGED
@@ -28,6 +28,8 @@
28
28
 
29
29
  ## 使い始める
30
30
 
31
+ `sequence` は SvelteKit の resolve オプションを保持します。ページ変換は内側から外側へ実行し、preload とシリアライズ対象ヘッダーのフィルターは最初の設定を優先します。
32
+
31
33
  ### インストール
32
34
 
33
35
  ```bash
@@ -57,7 +59,7 @@ const config = {
57
59
  kit: {
58
60
  adapter: adapter(),
59
61
  // 双构建模式:import { createAdapter } from '@h-ai/kit/adapter'
60
- // adapter: createAdapter(),
62
+ // adapter: await createAdapter(),
61
63
  },
62
64
  }
63
65
 
@@ -86,24 +88,28 @@ Crypto または Kit/IAM を使う場合、アプリの本番依存に `sm-crypt
86
88
 
87
89
  ```typescript
88
90
  import type { Handle } from '@sveltejs/kit'
91
+ import { cache } from '@h-ai/cache'
89
92
  import { kit } from '@h-ai/kit'
90
93
 
91
94
  const haiHandle = kit.createHandle({
92
95
  auth: {
93
96
  verifyToken: async (token) => {
94
- return token ? { userId: 'u_1', roles: ['admin'], permissions: ['user:read'] } : null
97
+ const verified = await iam.auth.verifyToken(token)
98
+ return verified.success ? verified.data : null
95
99
  },
96
100
  loginUrl: '/auth/login',
97
101
  protectedPaths: ['/admin/*', '/api/*'],
98
102
  publicPaths: ['/api/auth/*', '/api/public/*'],
99
103
  },
100
- rateLimit: { windowMs: 60_000, maxRequests: 100 },
104
+ rateLimit: { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' }) },
101
105
  logging: true,
102
106
  })
103
107
 
104
108
  export const handle: Handle = haiHandle
105
109
  ```
106
110
 
111
+ 最初の確定時点から固定ウィンドウを開始し、後続リクエストはTTLを延長しません。storeは必須です。共有Redisの初期化成功を確認し、全ノードのnamespace/windowMs/maxRequestsを一致させます。キャッシュ障害や競合上限は503、超過は429で、get+setやローカル計数へフォールバックしません。createMemoryRateLimitStoreは開発または明示HAI_E2E/HAI_E2E_MOCKテスト用で、常駐タイマーを使わず参照解放で回収されます。1リクエスト最大64項目を遅延走査し、上限10000ウィンドウです。満杯時は有効な計数を追い出さず新キーを503にし、外部storeのライフサイクルは作成者が管理します。本番でテスト変数を設定しません。ヘッダー時刻は推定、実際の期間はRedis TTLで制御するため時計を同期します。キーは信頼済みadapterのgetClientAddressのみ使い、等価IPv6/IPv4表記を正規化します。生の転送ヘッダーを信頼せず、代理信頼はadapterで設定します。
112
+
107
113
  #### API endpoint
108
114
 
109
115
  ```typescript
@@ -142,21 +148,26 @@ const response = await apiFetch('/api/users', { method: 'GET' })
142
148
  ```
143
149
 
144
150
  > CSRF Header は `apiFetch` が自動付与します。`@h-ai/kit` は独立した CSRF middleware ファクトリーを提供しません。
151
+ > `apiFetch` は文字列、URL、Request を受け付けます。Request のメソッド、ヘッダー、本文を保持し、書き込みには CSRF を付与します。transport 有効時は対応する本文のみ暗号化し、multipart の境界を保持します。
145
152
  >
146
- > 認証:同一オリジンの SvelteKit endpoint は httpOnly Cookie を優先し、サーバーで Cookie を読んで認証します。機密 Token を `localStorage` に保存する必要はありません。Bearer Header が必要なら独自の `BrowserTokenStore`、または `kit.auth.setBrowserToken()` でページ内メモリーに保存してから `kit.client.create({ auth: true })` を使います。
153
+ > 同一オリジンの SvelteKit endpoint は httpOnly Cookie を使い、サーバーで検証します。ブラウザーの Bearer Header が必要なら `kit.auth.setBrowserToken()` でページ内メモリーに保存してから `kit.client.create({ auth: true })` を使います。独自の `BrowserTokenStore` もメモリーまたはプラットフォームの安全なストレージを使用し、機密の認証情報を Web Storage に永続化しないでください。
147
154
 
148
155
  #### 同一オリジンの通信暗号化
149
156
 
150
- kit の通信暗号化は `@h-ai/crypto` に委譲します。サーバーは `crypto.transport.createServer()` で管理器を作成し、ブラウザーは `kit.client.installBrowserTransport(kitConfig, { crypto })` の 1 行で同一オリジンの fetch ラッパーを導入します。アプリ側はトップレベルの `crypto` のみを設定します。
157
+ kit の通信暗号化は `@h-ai/crypto` に委譲します。サーバーは `crypto.transport.createServer({ serverKeyRing, keyStore })` で管理器を作成し、ブラウザーは `kit.client.installBrowserTransport(kitConfig, { crypto })` の 1 行で同一オリジンの fetch ラッパーを導入します。サーバーには管理済み鍵環と共有 registry も渡します。
151
158
 
152
159
  **src/hooks.server.ts**:
153
160
 
154
161
  ```ts
155
162
  import { cache } from '@h-ai/cache'
156
- import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
163
+ import { createRedisTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
157
164
  import { kit } from '@h-ai/kit'
158
165
 
159
166
  await crypto.init()
167
+ const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
168
+ if (!ring.success)
169
+ throw new Error(ring.error.message)
170
+ const serverKeyRing = ring.data
160
171
  await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
161
172
 
162
173
  export const handle = kit.createHandle({
@@ -168,7 +179,7 @@ export const handle = kit.createHandle({
168
179
  crypto: {
169
180
  crypto,
170
181
  transport: {
171
- requireEncryption: true,
182
+ serverKeyRing,
172
183
  keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
173
184
  },
174
185
  },
@@ -203,11 +214,17 @@ export const { apiFetch } = kit.client.create()
203
214
 
204
215
  既定の鍵交換パスは `/api/_hai/key-exchange` です。サーバーで `transport.keyExchangePath` を変更した場合、クライアントの `transport.keyExchangeUrl` も合わせて設定してください。
205
216
 
206
- ノード間でクライアント公開鍵を共有するには、実行時に `kit.createHandle({ crypto: { transport: { keyStore } } })` へ `createRedisTransportKeyStore()` または `createReldbTransportKeyStore()` を渡します。このようなオブジェクト依存を `_kit.yml` に**書いてはいけません**。設定ファイルは公開パスとスイッチのみを保持します。
217
+ サーバーには同じ管理済み `serverKeyRing` と共有 `keyStore`(Redis または共有 DB)を明示的に渡します。クライアント公開鍵の共有だけではノード間で復号できません。暗黙のプロセス鍵・メモリー registry はなく、開発・テストでは `createInMemoryKeyStore(capacity)` を明示します。再起動後は同じ鍵環を読み込み、sticky session は不要です。
207
218
 
208
219
  transport は既定で同一オリジンの `/api/*`、SvelteKit の `__data.json`、鍵交換 endpoint に適用します。ページ文書・静的ファイルは平文で `X-Client-Id` は不要です。アップロードなどの `multipart/form-data` はそのまま送信し、サーバーの `excludePaths` で明示的に許可してください。
209
220
 
210
- 既定の安全方針は fail-closed です。`requireEncryption: true` で保護パスに `X-Client-Id` がなければ 400 を返します。管理器の利用不可、応答の暗号化失敗、暗号化サイズ上限超過はエラーになり、平文の業務応答は渡しません。`requireEncryption: false` は段階的移行用で、本番では推奨しません。
221
+ 認証済み v2 のみを受け付けます。独立したランダム SM4-CBC 鍵と HMAC-SM3 鍵を SM2 で包み、送信者署名、MAC の順に検証してから CBC を復号します。要求はクライアント秘密鍵、応答は選択されたサーバー鍵バージョンで署名します。version、direction、serverKeyId、clientId、nonce を兼ねるランダム 128 ビット requestId、issuedAt、HTTP method、pathname と元の search の SM3 pathHash、応答 status を認証します。要求の有効期限は 5 分、未来方向の時計差は最大 30 秒です。共有 registry が (clientId, requestId) を原子的に消費し、ノード間の時計差を含め issuedAt + 6 分まで保持します。保存障害では拒否します。時計同期と Redis の noeviction/永続化により有効な nonce を保持してください。業務の再試行には別の冪等キーが必要です。
222
+
223
+ 空の POST も空平文の認証封装を送ります。GET/HEAD は X-Hai-Transport ヘッダー、204/304/HEAD など本文なし応答も同じヘッダーを使います。他の要求・応答は JSON 本文と X-Encrypted: true を使います。クライアントは元の要求コンテキストと HTTP status を照合し、マーカー欠落、旧形式、認証失敗では reject します。平文 endpoint は excludePaths で明示的に除外し、CORS では X-Hai-Transport を許可・公開してください。鍵交換の公開鍵認証は TLS に依存し、IAM の業務認可は別に必要です。
224
+
225
+ `transport.maxPlaintextBytes` でリクエストとレスポンスの JSON 平文容量を共通設定します。単位は UTF-8 バイト、既定値は 1 MiB、有効範囲は 1 KiB~64 MiB の整数です。クライアントは鍵交換で上限を取得し、`resolveTransportBodyLimits()` が暗号文とプロキシ容量(平文の 2 倍 + 32 KiB)を算出します。鍵交換、公開鍵、認証ヘッダー、防リプレイ制約は独立です。超過時は元ストリームを停止して reader を解放します。要求は 413、応答は暗号化された 500 容量エラーとなり、`TransportBodyTooLargeError` / `hai:crypto:070` で識別できます。 [Transport capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md)。
226
+
227
+ serv/kit は要求の結合、署名、MAC を検証した後、時間窓を超えた報文に暗号化した HTTP 400 時刻エラーを返します。クライアントは `TransportTimeError`(`hai:crypto:071`)で端末とサーバーの時刻同期・再読み込みを案内します。`api-client` は `VALIDATION_FAILED` に変換し `cause.transportCode` に原因を保持します。自動再送やリプレイ時間窓の緩和は行わず、改ざん・未認証の要求は従来の認証エラーです。
211
228
 
212
229
  #### `_kit.yml` による共通 transport 設定
213
230
 
@@ -221,13 +238,10 @@ server:
221
238
 
222
239
  transport:
223
240
  keyExchangePath: /api/_hai/key-exchange
224
- requireEncryption: true
225
- encryptResponse: true
226
241
  excludePaths:
227
242
  - /api/storage
228
243
  - /api/public
229
244
  - /api/auth/profile/avatar
230
- maxClients: 10000
231
245
  ```
232
246
 
233
247
  ```ts
@@ -259,23 +273,30 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
259
273
 
260
274
  `crypto.encryptedCookies` を設定する場合、`crypto.cookieEncryptionKey` または `HAI_KIT_COOKIE_KEY` が必要です。鍵がなければ `createHandle()` ですぐエラーになります。保護対象 Cookie の平文・破損暗号文・復号失敗は未存在として扱い、暗号化失敗時も平文保存に切り替えません。
261
275
 
276
+ 認証 Cookie は `NODE_ENV` または `HAI_CORE_ENV` が `production` の場合に `Secure` を既定で有効にします。`HAI_E2E` / `HAI_E2E_MOCK` は Cookie の安全要件を緩和しません。本番のレート制限も両方の環境変数を認識し、明示的なテストフラグだけがメモリカウンターを許可します。
277
+
262
278
  ### エクスポートパス
263
279
 
280
+ 実行時メソッドは `kit` 名前空間または明示したサブパスを使用し、型は個別に公開します。browser 宣言はサーバーファクトリを公開しません。Vite 設定には `@sveltejs/kit/vite` を使い、`@h-ai/kit/vite` プラグインはありません。
281
+
264
282
  - `@h-ai/kit`:主入口。`kit` 名前空間と型を提供。
265
283
  - `@h-ai/kit/client`:ブラウザークライアント。
266
284
  - `@h-ai/kit/adapter`:2 モードのビルド adapter(`createAdapter()`)。
267
- - `@h-ai/kit/vite`:Vite プラグイン。
268
285
  - `@h-ai/kit/a2a`:A2A 統合。
269
286
  - `@h-ai/kit/crypto`:暗号モジュール統合。
270
287
 
271
288
  ### 設定による認証画面
272
289
 
290
+ パスワードログインの送信項目は `identifier` と `password` のみです。`AuthPanel` は `LoginForm` の `username` を `identifier` に変換します。有効期間はサーバーの `session.maxAge` / `session.refreshTokenMaxAge` で設定し、フォームにログインごとの永続セッション選択肢はありません。
291
+
273
292
  規約は設定の userAgreementMarkdown/privacyPolicyMarkdown または URL を使用できます。同じ規約は Markdown 本文を優先して安全な Modal で描画します。
274
293
 
275
294
  kit.auth.createApi(iam) の標準 handler と kit.auth.publicPaths を使用し、AuthPanel には kit.client.createAuthActions({ apiFetch }) を接続します。検証、Cookie、HaiResult 変換を共通化し、ユーザー情報と改密の保護を維持します。
276
295
 
277
296
  [完整 IAM 配置 / IAM configuration](../iam/README.ja.md)
278
297
 
298
+ `kit.auth.logout(cookies, accessToken)` は `Promise<HaiResult<void>>` を返します。Token がなければ Cookie のみ消去して成功し、Token があるのに認証操作が未設定なら失敗します。サーバー結果にかかわらず Cookie は消去しますが、呼出側は失敗を HTTP 応答や UI に伝えます。ローカル消去はサーバー失効の確認ではありません。制御フロー例外は引き続き伝播します。
299
+
279
300
  ### エラー処理
280
301
 
281
302
  - API endpoint は `kit.handler()` でラップすることを推奨。
@@ -286,12 +307,45 @@ kit.auth.createApi(iam) の標準 handler と kit.auth.publicPaths を使用し
286
307
 
287
308
  宣言型 CRUD 項目は UI フォームの幅を指定する `colSpan: 'auto' | 1 | 2` に対応します。`validation.message` は文字列または `() => string` を受け取り、現在の言語で表示できます。レイアウトは `CrudPage.form` で設定し、サーバーでも検証してください。
288
309
 
310
+ ### リクエスト言語の分離
311
+
312
+ Node リクエストと SSR は `core.i18n.runWithLocale(locale, () => handleRequest())` で囲みます。SvelteKit は `kit.i18n.runWithLocale(locale, () => resolve(event))` を使えます。serv はリクエストヘッダーから自動設定します。非同期の子処理も言語を継承し、明示的な `MessageOptions.locale`、リクエスト言語、起動時の既定値の順で優先します。コンテキスト内の `setGlobalLocale` はそのリクエストだけを変更します。コンテキスト外の設定は起動時に限定し、リクエスト hook から呼びません。
313
+
314
+ ブラウザーはアプリ全体の `setGlobalLocale` / `createLocaleStore` を使います。SSR の UI store 初期化と setter は自身の状態のみ変更し、メッセージはリクエストコンテキストを参照します。`runWithLocale` は Node 入口専用です。
315
+
289
316
  ## 開発とテスト
290
317
 
291
318
  ```bash
292
319
  pnpm --filter @h-ai/kit test
293
320
  ```
294
321
 
322
+ Kit に共通の init/close はありません。共有 cache・IAM・crypto はアプリが初期化と終了を管理します。型検査できる例は [JSDoc 規約(中国語)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证) を参照してください。認証は iam.auth.verifyToken で検証し、空でない token だけでは許可しません。
323
+
324
+ ## フィードバック入口とリクエスト関連付け
325
+
326
+ `kit.createFeedbackHandler` は `/api/feedback/[...path]/+server.ts` の GET/POST/PATCH に使用できます。serv と同じ api-contract schema を再利用し、入力ストリームの容量とクライアント認証情報の混入を制限します。resolveContext は検証済み locals.session から範囲・権限を作り、null は拒否します。既存 createHandle の認証・CSRF・通信暗号化を維持してください。
327
+
328
+ createHandle は有効な上限付き X-Request-Id を保持して応答に付け、非同期業務にリクエストと検証済みユーザーを伝播します。通常の業務ログを収集する場合、ホストの middleware で `core.context.run({ scopeId: 'configured-product' }, next)` により業務を包みます。範囲は feedback と一致させ、クライアントのヘッダーから取得しません。
329
+
330
+ ```ts
331
+ import type { SessionData } from '@h-ai/kit'
332
+ import { feedback } from '@h-ai/feedback'
333
+ import { kit } from '@h-ai/kit'
334
+
335
+ const handler = kit.createFeedbackHandler({
336
+ feedback,
337
+ resolveContext: (event) => {
338
+ const session = (event.locals as { session?: SessionData }).session
339
+ return session
340
+ ? { scopeId: 'configured-product', userId: session.userId, permissions: session.permissions }
341
+ : null
342
+ },
343
+ })
344
+ export const GET = handler
345
+ export const POST = handler
346
+ export const PATCH = handler
347
+ ```
348
+
295
349
  ## ライセンス
296
350
 
297
351
  Apache-2.0
package/README.md CHANGED
@@ -28,6 +28,8 @@
28
28
 
29
29
  ## 使用入门
30
30
 
31
+ `sequence` 保留并组合 SvelteKit 的 `resolve` 选项:页面转换由内向外执行,`preload` 与序列化响应头过滤使用首个配置,确保 i18n 的 HTML 语言标记正确替换。
32
+
31
33
  ### 安装
32
34
 
33
35
  ```bash
@@ -57,7 +59,7 @@ const config = {
57
59
  kit: {
58
60
  adapter: adapter(),
59
61
  // 双构建模式:import { createAdapter } from '@h-ai/kit/adapter'
60
- // adapter: createAdapter(),
62
+ // adapter: await createAdapter(),
61
63
  },
62
64
  }
63
65
 
@@ -86,24 +88,28 @@ export default defineConfig({
86
88
 
87
89
  ```typescript
88
90
  import type { Handle } from '@sveltejs/kit'
91
+ import { cache } from '@h-ai/cache'
89
92
  import { kit } from '@h-ai/kit'
90
93
 
91
94
  const haiHandle = kit.createHandle({
92
95
  auth: {
93
96
  verifyToken: async (token) => {
94
- return token ? { userId: 'u_1', roles: ['admin'], permissions: ['user:read'] } : null
97
+ const verified = await iam.auth.verifyToken(token)
98
+ return verified.success ? verified.data : null
95
99
  },
96
100
  loginUrl: '/auth/login',
97
101
  protectedPaths: ['/admin/*', '/api/*'],
98
102
  publicPaths: ['/api/auth/*', '/api/public/*'],
99
103
  },
100
- rateLimit: { windowMs: 60_000, maxRequests: 100 },
104
+ rateLimit: { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' }) },
101
105
  logging: true,
102
106
  })
103
107
 
104
108
  export const handle: Handle = haiHandle
105
109
  ```
106
110
 
111
+ 限流采用首次成功提交为起点的固定窗口,后续请求不延长 TTL。必须显式传 store;应用先检查共享 Redis 初始化结果,所有节点使用相同 namespace/windowMs/maxRequests。缓存读取/条件提交失败或冲突耗尽返回503,超限429;不会回退 get+set 或本地计数。createMemoryRateLimitStore 仅开发或显式 HAI_E2E/HAI_E2E_MOCK 测试使用,无常驻定时器,丢弃引用即可回收;每次请求最多惰性扫描 64 项,最多保存 10000 个窗口,容量满时对新 key 返回503且不驱逐有效计数,外部 store 生命周期由创建者管理;生产不得设置测试开关。响应重置时间为节点时钟估算,实际窗口由 Redis TTL 决定,须同步时钟。默认键只读取可信 adapter 的 getClientAddress、规范化等价IPv6和IPv4映射地址,不采信原始转发头;代理信任通过部署adapter配置。
112
+
107
113
  #### API 端点
108
114
 
109
115
  ```typescript
@@ -142,21 +148,26 @@ const response = await apiFetch('/api/users', { method: 'GET' })
142
148
  ```
143
149
 
144
150
  > CSRF Header 注入由 `apiFetch` 自动处理;`@h-ai/kit` 不提供独立的 CSRF 中间件工厂。
151
+ > `apiFetch` 接受字符串、URL 或 Request;Request 自带的方法、请求头与正文会保留,写方法同样附加 CSRF。启用 transport 时仅加密可支持的正文,multipart 上传保持原始边界。
145
152
  >
146
- > 认证建议:同源 SvelteKit endpoint 优先使用 httpOnly Cookie,由服务端读取 Cookie 完成鉴权,浏览器端不需要把敏感 Token 写入 `localStorage`。如确需注入 Bearer Header,请传入自定义 `BrowserTokenStore`,或先用 `kit.auth.setBrowserToken()` 写入页面内存后再使用 `kit.client.create({ auth: true })`。
153
+ > 同源 SvelteKit endpoint 使用 httpOnly Cookie,由服务端读取 Cookie 完成鉴权。浏览器 Bearer Token 使用 `kit.auth.setBrowserToken()` 写入页面内存,再通过 `kit.client.create({ auth: true })` 注入。自定义 `BrowserTokenStore` 也必须使用内存或平台安全存储,禁止将敏感凭据写入 Web Storage。
147
154
 
148
155
  #### 同源传输加密
149
156
 
150
- kit 的传输加密也统一委托 `@h-ai/crypto`:服务端通过 `crypto.transport.createServer()` 创建管理器,浏览器端通过 `kit.client.installBrowserTransport(kitConfig, { crypto })` 一行安装同源 fetch 包装。应用层只配置顶层 `crypto` 句柄。
157
+ kit 的传输加密也统一委托 `@h-ai/crypto`:服务端通过 `crypto.transport.createServer({ serverKeyRing, keyStore })` 创建管理器,浏览器端通过 `kit.client.installBrowserTransport(kitConfig, { crypto })` 一行安装同源 fetch 包装。服务端还须注入受管密钥环和共享 registry。
151
158
 
152
159
  **src/hooks.server.ts**:
153
160
 
154
161
  ```ts
155
162
  import { cache } from '@h-ai/cache'
156
- import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
163
+ import { createRedisTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
157
164
  import { kit } from '@h-ai/kit'
158
165
 
159
166
  await crypto.init()
167
+ const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
168
+ if (!ring.success)
169
+ throw new Error(ring.error.message)
170
+ const serverKeyRing = ring.data
160
171
  await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
161
172
 
162
173
  export const handle = kit.createHandle({
@@ -168,7 +179,7 @@ export const handle = kit.createHandle({
168
179
  crypto: {
169
180
  crypto,
170
181
  transport: {
171
- requireEncryption: true,
182
+ serverKeyRing,
172
183
  keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
173
184
  },
174
185
  },
@@ -203,11 +214,17 @@ export const { apiFetch } = kit.client.create()
203
214
 
204
215
  默认密钥协商路径为 `/api/_hai/key-exchange`。如服务端自定义 `transport.keyExchangePath`,客户端需同步配置 `transport.keyExchangeUrl`。
205
216
 
206
- 如需多节点共享客户端公钥,可在运行时为 `kit.createHandle({ crypto: { transport: { keyStore } } })` 注入 `createRedisTransportKeyStore()` 或 `createReldbTransportKeyStore()`;这类对象型依赖**不应**写入 `_kit.yml`,配置文件仍只保存公开路径与开关。
217
+ 服务端必须显式注入相同的受管 `serverKeyRing` 和共享 `keyStore`(Redis 或共享数据库)。仅共享客户端公钥不足以跨节点解密。没有隐式进程私钥或内存 registry 回退;开发/测试显式用 `createInMemoryKeyStore(capacity)`。服务端重启后重新加载同一密钥环即可,无需 sticky session。
207
218
 
208
219
  transport 默认作用于同源 `/api/*` endpoint 与 SvelteKit `__data.json` 页面数据请求(以及密钥协商端点本身);页面文档与静态资源仍保持明文,不要求携带 `X-Client-Id`。文件上传等 `multipart/form-data` 请求会保持原样发送,需在服务端 `excludePaths` 中显式放行对应上传路径。
209
220
 
210
- 安全策略默认 fail-closed:`requireEncryption: true` 时,受保护路径缺少 `X-Client-Id` 会直接返回 400;服务端 transport 管理器不可用、响应体无法加密或超过单次加密上限时返回错误,不会把业务明文响应透传给客户端。`requireEncryption: false` 仅用于迁移期灰度,不建议在生产启用。
221
+ 认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求有效期 5 分钟,未来时钟偏差最多 30 秒;共享 registry 原子消费 (clientId, requestId),nonce 至少保留到 issuedAt + 6 分钟以覆盖节点偏差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
222
+
223
+ 空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口只能经 excludePaths 显式排除。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
224
+
225
+ 业务容量通过 `transport.maxPlaintextBytes` 统一配置:默认 1 MiB,整数范围 1 KiB~64 MiB,请求和响应共用 UTF-8 明文字节预算。客户端从握手自动获取上限,密文与部署代理预算由 `resolveTransportBodyLimits()` 推导(两倍明文 + 32 KiB)。握手、公钥、认证头和防重放约束独立;超限取消原流并释放 reader,请求 413、响应为加密的 500 容量错误,客户端识别 `TransportBodyTooLargeError` / `hai:crypto:070`。 [Transport capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md)。
226
+
227
+ serv/kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的报文返回加密的 HTTP 400 时间错误;客户端抛 `TransportTimeError`(`hai:crypto:071`),提示同步设备和服务器时间后刷新重试。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 保留该原因。不会自动重放请求或放宽防重放窗口;篡改或未认证报文仍返回通用认证失败。
211
228
 
212
229
  #### 使用 `_kit.yml` 统一 transport 配置
213
230
 
@@ -221,13 +238,10 @@ server:
221
238
 
222
239
  transport:
223
240
  keyExchangePath: /api/_hai/key-exchange
224
- requireEncryption: true
225
- encryptResponse: true
226
241
  excludePaths:
227
242
  - /api/storage
228
243
  - /api/public
229
244
  - /api/auth/profile/avatar
230
- maxClients: 10000
231
245
  ```
232
246
 
233
247
  ```ts
@@ -259,23 +273,30 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
259
273
 
260
274
  配置 `crypto.encryptedCookies` 时必须同时提供 `crypto.cookieEncryptionKey`,或设置 `HAI_KIT_COOKIE_KEY`。缺少密钥会在 `createHandle()` 时立即报错;受保护 Cookie 的明文、损坏密文或解密失败都按不存在处理,加密失败也不会回退写入明文。
261
275
 
276
+ 认证 Cookie 的 `Secure` 默认值在 `NODE_ENV` 或 `HAI_CORE_ENV` 为 `production` 时开启,`HAI_E2E` / `HAI_E2E_MOCK` 不降低 Cookie 安全要求。生产限流同样识别这两个环境标志;仅显式测试开关可放宽内存计数器限制。
277
+
262
278
  ### 导出路径
263
279
 
280
+ 子模块运行时方法通过 `kit` 命名空间或明确子路径使用,类型单独导出;浏览器声明不暴露服务端工厂。Vite 配置使用 `@sveltejs/kit/vite`,不存在 `@h-ai/kit/vite` 插件。
281
+
264
282
  - `@h-ai/kit`:主入口,提供 `kit` 命名空间与类型。
265
283
  - `@h-ai/kit/client`:浏览器端客户端。
266
284
  - `@h-ai/kit/adapter`:双构建 adapter(`createAdapter()`)。
267
- - `@h-ai/kit/vite`:Vite 插件。
268
285
  - `@h-ai/kit/a2a`:A2A 协议集成。
269
286
  - `@h-ai/kit/crypto`:加密模块集成。
270
287
 
271
288
  ### 配置驱动认证页面
272
289
 
290
+ 密码登录只提交 `identifier` 和 `password`;`AuthPanel` 将 `LoginForm` 的 `username` 映射为 `identifier`。会话有效期由服务端 `session.maxAge` / `session.refreshTokenMaxAge` 配置决定,登录表单不提供按次登录的持久会话选项。
291
+
273
292
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
274
293
 
275
294
  路由直接导出 `kit.auth.createApi(iam)` 的 login、loginWithOtp、loginWithLdap、register、publicConfig、sendOtp、requestPasswordReset、confirmPasswordReset handler;自动执行 schema 验证、Cookie 设置和 HaiResult 映射。匿名白名单使用 `kit.auth.publicPaths`,不会放开 /me 和改密端点。浏览器用 `kit.client.createAuthActions({ apiFetch })` 直接连接 AuthPanel,复用 CSRF/transport。
276
295
 
277
296
  [完整 IAM 配置 / IAM configuration](../iam/README.md)
278
297
 
298
+ `kit.auth.logout(cookies, accessToken)` 返回 `Promise<HaiResult<void>>`。无令牌时仅清除 Cookie 并成功;有令牌但未配置认证操作时返回失败。无论服务端结果如何都清除本地 Cookie,调用方仍必须把失败转换为错误响应或提示;仅成功结果代表服务端撤销已经确认。底层控制流异常继续传播。
299
+
279
300
  ### 错误处理
280
301
 
281
302
  - 推荐使用 `kit.handler()` 包装 API 端点。
@@ -286,12 +307,45 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
286
307
 
287
308
  声明式 CRUD 字段支持 `colSpan: 'auto' | 1 | 2`,供 UI 双列表单决定字段宽度;`validation.message` 支持字符串或 `() => string`,便于随当前语言显示校验错误。UI 布局在 `CrudPage.form` 上配置,服务端仍须独立校验。
288
309
 
310
+ ### 请求语言隔离
311
+
312
+ Node 请求与 SSR 使用 `core.i18n.runWithLocale(locale, () => handleRequest())`;SvelteKit 可使用 `kit.i18n.runWithLocale(locale, () => resolve(event))`,serv 自动按请求头建立上下文。异步子任务继承请求语言,优先级为显式 `MessageOptions.locale`、请求语言、进程启动默认语言。`setGlobalLocale` 在请求上下文中只更新当前请求;上下文外仅用于启动默认值,不能在请求 hook 中调用它修改默认值。
313
+
314
+ 浏览器继续使用应用级 `setGlobalLocale` / `createLocaleStore`。SSR 的 UI store 初始化和 setter 只修改自身状态,不写全局语言;UI 消息从请求上下文读取。`runWithLocale` 仅支持 Node 入口,浏览器通过显式 locale 或应用状态翻译。
315
+
289
316
  ## 开发测试
290
317
 
291
318
  ```bash
292
319
  pnpm --filter @h-ai/kit test
293
320
  ```
294
321
 
322
+ 模块无统一 init/close;共享缓存、IAM 与 crypto 由应用启动时初始化并统一关闭。示例使用真实公开入口并作类型检查,见[模块 JSDoc 约定](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)。请求认证必须调用 iam.auth.verifyToken,不能把非空 token 视为有效身份。
323
+
324
+ ## 反馈接口与普通请求关联
325
+
326
+ `kit.createFeedbackHandler` 可作为 `/api/feedback/[...path]/+server.ts` 的 GET/POST/PATCH handler,复用 api-contract schema,与 serv 使用同一数据契约;输入流受大小上限约束,客户端身份/租户字段被拒绝。宿主通过 resolveContext 从已经验证的 locals.session 映射范围和权限,null 拒绝访问,并保留现有 createHandle 的 CSRF、认证与传输加密。
327
+
328
+ createHandle 保留有效且有界的入站 X-Request-Id,补写响应头,并为异步业务传递请求 ID 和已验证用户。需要收集普通业务后端日志时,在宿主 middleware 中以 `core.context.run({ scopeId: 'configured-product' }, next)` 包住实际业务;该可信范围应与反馈接口一致,不能从客户端头推导。
329
+
330
+ ```ts
331
+ import type { SessionData } from '@h-ai/kit'
332
+ import { feedback } from '@h-ai/feedback'
333
+ import { kit } from '@h-ai/kit'
334
+
335
+ const handler = kit.createFeedbackHandler({
336
+ feedback,
337
+ resolveContext: (event) => {
338
+ const session = (event.locals as { session?: SessionData }).session
339
+ return session
340
+ ? { scopeId: 'configured-product', userId: session.userId, permissions: session.permissions }
341
+ : null
342
+ },
343
+ })
344
+ export const GET = handler
345
+ export const POST = handler
346
+ export const PATCH = handler
347
+ ```
348
+
295
349
  ## 许可证
296
350
 
297
351
  Apache-2.0