@hcmai/sdk 0.3.7 → 0.3.9
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/dist/index.d.ts +464 -1
- package/dist/index.js +1236 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http/trace.ts","../src/http/client.ts","../src/http/interceptors.ts","../src/package-version.ts","../src/auth/token-store.ts","../src/config/paths.ts","../src/auth/principal.ts","../src/auth/password.ts","../src/auth/refresh.ts","../src/config/profile.ts","../src/config/global-config.ts","../src/config/env.ts","../src/errors/codes.ts","../src/errors/cli-error.ts","../src/config/identity.ts","../src/config/migration.ts","../src/config/resolve.ts","../src/models/query.ts","../src/models/action.ts","../src/meta/describe.ts","../src/models/create.ts","../src/models/update.ts","../src/models/remove.ts","../src/import/fields.ts","../src/import/placeholder.ts","../src/import/parse.ts","../src/import/runner.ts","../src/client.ts","../src/documents.ts","../src/output/json.ts","../src/output/yaml.ts","../src/output/table.ts","../src/chat/display-width.ts","../src/output/index.ts","../src/auth/client-credentials.ts","../src/auth/pat.ts","../src/auth/pairing.ts","../src/auth/change-password.ts","../src/auth/admin-reset-password.ts","../src/chat/ws-client.ts","../src/chat/stream-iterator.ts","../src/chat/control-channel.ts","../src/chat/delegation-constants.ts","../src/chat/v4-reducer.ts","../src/chat/one-shot.ts","../src/chat/conversation-state.ts","../src/chat/conversation.ts","../src/chat/resume-summary.ts","../src/chat/conversation-list.ts","../src/mini/types.ts","../src/mini/workspace-files.ts","../src/mini/project.ts","../src/mini/templates.ts","../src/cache.ts","../src/tenant.ts","../src/meta/tenant-meta.ts","../src/skills.ts","../src/settings.ts","../src/fde/layout.ts","../src/fde/fingerprint.ts","../src/fde/doctor.ts","../src/fde/probe.ts","../src/fde/recon/runner.ts","../src/fde/recon/carry-over.ts","../src/fde/recon/notes.ts","../src/fde/recon/crawl.ts","../src/fde/recon/fields.ts","../src/fde/recon/gates.ts","../src/fde/recon/scc.ts","../src/fde/recon/topology.ts","../src/fde/recon/mapping.ts","../src/fde/recon/modelsmd.ts","../src/fde/recon/pointer-audit.ts","../src/fde/recon/refs.ts","../src/fde/recon/valuedomain.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\n\nexport interface ParsedTraceparent {\n version: string;\n traceId: string;\n spanId: string;\n flags: string;\n}\n\nexport function newTraceparent(): string {\n const traceId = randomBytes(16).toString('hex');\n const spanId = randomBytes(8).toString('hex');\n return `00-${traceId}-${spanId}-01`;\n}\n\nexport function parseTraceparent(value: string): ParsedTraceparent | null {\n const m = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/.exec(value);\n if (!m) return null;\n return { version: m[1]!, traceId: m[2]!, spanId: m[3]!, flags: m[4]! };\n}\n","import axios, { type AxiosInstance } from 'axios';\nimport { attachAuth, attachTrace, attachRetry } from './interceptors';\nimport { SDK_VERSION } from '../package-version';\n\nexport interface HttpClientOptions {\n endpoint: string;\n getAccessToken: () => Promise<string | null>;\n onRefresh: () => Promise<void>;\n timeoutMs?: number;\n userAgent?: string;\n}\n\nexport function createHttpClient(opts: HttpClientOptions): AxiosInstance {\n const client = axios.create({\n baseURL: opts.endpoint,\n timeout: opts.timeoutMs ?? 60_000,\n headers: { 'User-Agent': opts.userAgent ?? `hcmai-sdk/${SDK_VERSION}` },\n });\n attachTrace(client);\n attachAuth(client, opts.getAccessToken);\n attachRetry(client, opts.onRefresh);\n return client;\n}\n","import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios';\nimport { newTraceparent } from './trace';\n\ndeclare module 'axios' {\n export interface AxiosRequestConfig {\n /**\n * 该请求**不参与** 401 自动 refresh + retry。\n *\n * 🔴 续期请求自己必须打上它:`attachRetry` 在 401 上调 `onRefresh()`,而\n * onRefresh 最终会发一个续期请求;那个请求的 config 是新的、没有 `__retried`\n * 标记,于是它的 401 又触发一次 onRefresh —— 每层发一个新请求,**无界递归**。\n * 见 `auth/refresh.ts` 的 slidePasswordSession。\n */\n __skipAuthRetry?: boolean;\n }\n}\n\nexport function attachTrace(client: AxiosInstance): void {\n client.interceptors.request.use((cfg: InternalAxiosRequestConfig) => {\n cfg.headers.set('traceparent', newTraceparent());\n return cfg;\n });\n}\n\nexport function attachAuth(client: AxiosInstance, getToken: () => Promise<string | null>): void {\n client.interceptors.request.use(async (cfg) => {\n if (!cfg.headers.has('Authorization')) {\n const tok = await getToken();\n if (tok) cfg.headers.set('Authorization', `Bearer ${tok}`);\n }\n return cfg;\n });\n}\n\nexport function attachRetry(client: AxiosInstance, onRefresh: () => Promise<void>): void {\n client.interceptors.response.use(\n (resp) => resp,\n async (error) => {\n const status = error.response?.status;\n const cfg = error.config as (InternalAxiosRequestConfig & { __retried?: boolean }) | undefined;\n if (!cfg) throw error;\n\n // 401 单次自动 refresh + retry。__skipAuthRetry 的请求(续期请求自己)直接放行,\n // 否则 401 → onRefresh → 续期请求 → 401 会无界递归。\n if (status === 401 && !cfg.__retried && !cfg.__skipAuthRetry) {\n cfg.__retried = true;\n try {\n await onRefresh();\n } catch {\n throw error;\n }\n // 关键:删除过期 Authorization,让 attachAuth 重新取刷新后的 token\n cfg.headers.delete('Authorization');\n return client.request(cfg);\n }\n\n // 5xx 指数退避 retry 2 次\n if (status && status >= 500 && status < 600) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- 在 config 上挂 retry 计数标记\n const attempts = (cfg as any).__attempts ?? 0;\n if (attempts < 2) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- 在 config 上挂 retry 计数标记\n (cfg as any).__attempts = attempts + 1;\n const delay = 500 * Math.pow(3, attempts);\n await new Promise((r) => setTimeout(r, delay));\n // 防御性:若 token 临过期,让 attachAuth 重新取\n cfg.headers.delete('Authorization');\n return client.request(cfg);\n }\n }\n throw error;\n },\n );\n}\n","import { readFileSync } from 'node:fs';\n\ninterface PackageManifest {\n version: string;\n}\n\nconst manifest = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8'),\n) as PackageManifest;\n\nexport const SDK_VERSION = manifest.version;\n","import * as fs from 'node:fs/promises';\nimport type * as Keytar from 'keytar';\nimport { credentialsFile, profileDir, identityDir, identityMetaFile } from '../config/paths';\n\n/**\n * 凭据的来路。\n *\n * 🔴 **来路 ≠ 语义**:`password` 与 `pairing` 是两条完全不同的取得方式,但换来的是\n * **同一种东西**——`TokenManager.createToken` 铸的服务端会话 token(滑动续期、\n * 可 `/api/auth/refresh`、可 `/api/auth/logout` 单杀)。而 `pat` 与 `client_credentials`\n * 不是。判「能不能续期 / 该不该请服务端吊销」必须走 {@link isServerSlidingSession},\n * 不要拿 `=== 'password'` 去比——见该函数的说明。\n */\nexport type AuthScheme = 'client_credentials' | 'pat' | 'password' | 'pairing';\n\n/**\n * 这条凭据是不是「服务端会话 token」——即后端在每个请求上滑动续期、且\n * `/api/auth/logout` 能单条吊销的那一类。\n *\n * 🔴 **存在的理由是防一类静默退化**:全仓有三处要问这个问题——`refreshToken` 的\n * scheme 分支、`ensureSessionFresh` 的早退、`logout` 的 `revokeOnServer`。散着写成三个\n * 字面量比较,下次加第五轨时必然漏掉其中一处,而漏掉的后果**没有任何报错**:\n * - 漏在 refresh 分支 → 落到 default 抛「unknown auth scheme」,会话再也续不上\n * - 漏在 ensureSessionFresh → 不再主动续期,退回「每天重登」\n * - 漏在 revokeOnServer → **`logout` 静默跳过服务端吊销**,而 ADR-308 把\n * 「用完跑 hcm logout」写成了顾问在客户主机上那一行**唯一可操作的缓解**\n *\n * 所以这里收敛成一个谓词,三处都调它。\n */\nexport function isServerSlidingSession(scheme: AuthScheme): boolean {\n return scheme === 'password' || scheme === 'pairing';\n}\n\nexport interface TokenRecord {\n scheme: AuthScheme;\n accessTokenRef: string; // \"keychain:<svc>/<acct>\" | \"file:<path>\" | \"plain:<token>\"\n refreshTokenRef?: string;\n expiresAt: string; // ISO 8601\n scopes: string[];\n /**\n * 签发时服务端告知的 TTL(秒)。**续期后本地 expiresAt 只能靠它重算**:后端\n * `POST /api/auth/refresh` 的响应体是 `{\"message\":\"token刷新成功\"}` —— 不回新的\n * 过期时间,而它重设的 Redis TTL 用的正是签发时那个全局配置值\n * (`SecurityProperties.token.expiration`,缺省 24h)。不存这个数,续期成功之后就只能\n * 猜一个 TTL 写回本地,猜小了照样早退、猜大了会放过一个已死的 token。\n *\n * 🔴 缺省(旧记录升级上来)时按 DEFAULT_SESSION_TTL_SECONDS 兜底,见 refresh.ts。\n */\n ttlSeconds?: number;\n}\n\nconst SERVICE_PREFIX = 'hcm-cli';\ntype KeytarModule = Pick<typeof Keytar, 'deletePassword' | 'getPassword' | 'setPassword'>;\n\ninterface TokenStoreInternal {\n __identity?: boolean;\n}\n\nexport class TokenStore {\n constructor(private profile: string) {}\n\n /**\n * Factory: build an identity-scoped TokenStore. Stores meta at\n * `identities/<name>/credentials.json` and uses keychain account prefix\n * `identity-<name>` to isolate from legacy profile-scoped stores.\n *\n * Used by T13 login when writing identity meta and by T15 business\n * commands when reading the active identity's token.\n */\n static forIdentity(name: string): TokenStore {\n const ts = new TokenStore(name);\n // Freeze the sentinel so Object.assign / shallow merges can't silently flip\n // an identity store back to profile mode. A subclass `IdentityTokenStore extends\n // TokenStore` would be type-safer, but the sentinel keeps the public API one-class.\n Object.defineProperty(ts, '__identity', { value: true, writable: false, configurable: false });\n return ts;\n }\n\n private get isIdentity(): boolean {\n // Strict === true: defends against accidental non-boolean assignment via cast hole.\n return (this as TokenStoreInternal).__identity === true;\n }\n\n private metaFile(): string {\n return this.isIdentity ? identityMetaFile(this.profile) : credentialsFile(this.profile);\n }\n\n private dataDir(): string {\n return this.isIdentity ? identityDir(this.profile) : profileDir(this.profile);\n }\n\n private keychainAccountPrefix(): string {\n return this.isIdentity ? `identity-${this.profile}` : this.profile;\n }\n\n async writeMeta(record: TokenRecord): Promise<void> {\n await fs.mkdir(this.dataDir(), { recursive: true });\n await fs.writeFile(this.metaFile(), JSON.stringify(record, null, 2), { mode: 0o600 });\n }\n\n async readMeta(): Promise<TokenRecord | null> {\n try {\n const raw = await fs.readFile(this.metaFile(), 'utf-8');\n return JSON.parse(raw) as TokenRecord;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw e;\n }\n }\n\n async setAccessToken(token: string): Promise<string> {\n if (await this.canUseKeychain()) {\n const keytar = await loadKeytar();\n if (!keytar) return this.writeTokenFile('access', token);\n const account = `${this.keychainAccountPrefix()}/access`;\n await keytar.setPassword(SERVICE_PREFIX, account, token);\n return `keychain:${SERVICE_PREFIX}/${account}`;\n }\n // fallback: 0600 文件\n return this.writeTokenFile('access', token);\n }\n\n async setRefreshToken(token: string): Promise<string> {\n if (await this.canUseKeychain()) {\n const keytar = await loadKeytar();\n if (!keytar) return this.writeTokenFile('refresh', token);\n const account = `${this.keychainAccountPrefix()}/refresh`;\n await keytar.setPassword(SERVICE_PREFIX, account, token);\n return `keychain:${SERVICE_PREFIX}/${account}`;\n }\n return this.writeTokenFile('refresh', token);\n }\n\n async getAccessToken(): Promise<string | null> {\n // 1. env override\n if (process.env.HCM_PAT) return process.env.HCM_PAT;\n if (process.env.HCM_ACCESS_TOKEN) return process.env.HCM_ACCESS_TOKEN;\n // 2. read meta + resolve ref\n const meta = await this.readMeta();\n if (!meta) return null;\n return this.resolveRef(meta.accessTokenRef);\n }\n\n async getRefreshToken(): Promise<string | null> {\n const meta = await this.readMeta();\n if (!meta?.refreshTokenRef) return null;\n return this.resolveRef(meta.refreshTokenRef);\n }\n\n async clear(): Promise<void> {\n const meta = await this.readMeta();\n if (!meta) return;\n // accessTokenRef may be absent on stub identities (created before first login)\n if (meta.accessTokenRef?.startsWith('keychain:')) {\n const account = meta.accessTokenRef\n .slice('keychain:'.length)\n .split('/')\n .slice(1)\n .join('/');\n const keytar = await loadKeytar();\n if (keytar) await keytar.deletePassword(SERVICE_PREFIX, account);\n } else if (meta.accessTokenRef?.startsWith('file:')) {\n await fs.rm(meta.accessTokenRef.slice('file:'.length), { force: true });\n }\n if (meta.refreshTokenRef?.startsWith('keychain:')) {\n const account = meta.refreshTokenRef\n .slice('keychain:'.length)\n .split('/')\n .slice(1)\n .join('/');\n const keytar = await loadKeytar();\n if (keytar) await keytar.deletePassword(SERVICE_PREFIX, account);\n } else if (meta.refreshTokenRef?.startsWith('file:')) {\n await fs.rm(meta.refreshTokenRef.slice('file:'.length), { force: true });\n }\n // Profile-scoped stores own the meta file (legacy logout) → delete it.\n // Identity-scoped stores share the meta file with deleteIdentity (T11\n // cascade ownership boundary) — leave it for deleteIdentity to clean up.\n if (!this.isIdentity) {\n await fs.rm(this.metaFile(), { force: true });\n }\n }\n\n private async resolveRef(ref: string): Promise<string | null> {\n if (ref.startsWith('keychain:')) {\n const account = ref.slice('keychain:'.length).split('/').slice(1).join('/');\n const keytar = await loadKeytar();\n if (!keytar) return null;\n return keytar.getPassword(SERVICE_PREFIX, account);\n }\n if (ref.startsWith('file:')) {\n try {\n return await fs.readFile(ref.slice('file:'.length), 'utf-8');\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw e;\n }\n }\n if (ref.startsWith('plain:')) return ref.slice('plain:'.length);\n return null;\n }\n\n private async canUseKeychain(): Promise<boolean> {\n const keytar = await loadKeytar();\n if (!keytar) return false;\n try {\n // sanity probe\n await keytar.getPassword(SERVICE_PREFIX, '__probe__');\n return true;\n } catch {\n return false;\n }\n }\n\n private async writeTokenFile(kind: 'access' | 'refresh', token: string): Promise<string> {\n const file = `${this.metaFile()}.${kind}`;\n await fs.mkdir(this.dataDir(), { recursive: true });\n await fs.writeFile(file, token, { mode: 0o600 });\n if (kind === 'access') {\n // eslint-disable-next-line no-console\n console.warn(`[hcm] WARNING: OS keychain unavailable. Token saved to ${file} (0600).`);\n }\n return `file:${file}`;\n }\n}\n\nasync function loadKeytar(): Promise<KeytarModule | null> {\n try {\n const mod = (await import('keytar')) as unknown as KeytarModule & {\n default?: KeytarModule;\n };\n return mod.default ?? mod;\n } catch {\n return null;\n }\n}\n","import * as path from 'node:path';\nimport * as os from 'node:os';\n\nexport function hcmConfigDir(): string {\n const xdg = process.env.XDG_CONFIG_HOME;\n if (xdg) return path.join(xdg, 'hcm');\n const home = process.env.HOME ?? os.homedir();\n return path.join(home, '.config', 'hcm');\n}\n\nexport function profileDir(profile: string): string {\n return path.join(hcmConfigDir(), profile);\n}\n\nexport function profileFile(profile: string): string {\n return path.join(profileDir(profile), 'profile.yml');\n}\n\nexport function credentialsFile(profile: string): string {\n return path.join(profileDir(profile), 'credentials.json');\n}\n\nexport function globalConfigFile(): string {\n return path.join(hcmConfigDir(), 'config.yml');\n}\n\nexport function conversationStateFile(profile: string): string {\n return path.join(profileDir(profile), 'last-conversation.json');\n}\n\nexport function replHistoryFile(profile: string): string {\n return path.join(profileDir(profile), 'repl-history');\n}\n\nexport function envsDir(): string {\n return path.join(hcmConfigDir(), 'envs');\n}\n\nexport function envDir(name: string): string {\n return path.join(envsDir(), name);\n}\n\nexport function envFile(name: string): string {\n return path.join(envDir(name), 'env.yml');\n}\n\nexport function identitiesDir(): string {\n return path.join(hcmConfigDir(), 'identities');\n}\n\nexport function identityDir(name: string): string {\n return path.join(identitiesDir(), name);\n}\n\nexport function identityMetaFile(name: string): string {\n return path.join(identityDir(name), 'credentials.json');\n}\n\nexport function archiveDir(): string {\n return path.join(hcmConfigDir(), '_archive');\n}\n","import type { TokenRecord } from './token-store';\n\n/**\n * **服务端认定的 token 归属**——与\"调用方以为的归属\"是两回事,两者不一致时\n * 一切看起来都正常,而你在对着另一个租户干活。\n *\n * 这正是 fde-kit 那边 Ruling 30 防的同一件事:`hcm login --tenant 1` 传进去的是\n * 「我要的租户」,服务端回的 `user.tenantId` 才是「token 实际归属的租户」。此前\n * login 成功那行只回显前者,于是登错租户是静默的。\n */\nexport interface Principal {\n /** 服务端认定的租户。ADR-177 之后新租户是 UUID,别按 int 处理。 */\n tenantId?: string;\n username?: string;\n roles?: string[];\n}\n\n/** 登录helper 的统一返回:凭据元数据 + 服务端认定的归属(拿不到时 undefined)。 */\nexport interface LoginOutcome {\n record: TokenRecord;\n /**\n * 🔴 `undefined` = **服务端没在响应里给身份**,与「给了但字段为空」是两个状态。\n * 前者的下一步是问服务端为什么不给,后者的下一步是看那个账号本身。合并之后\n * 回显只能给出一句放之四海而皆准的废话。\n */\n principal?: Principal;\n}\n\n/**\n * 从服务端响应里的 user/userInfo 对象抽出 Principal。\n *\n * 两个来源字段名一致但形状不同,所以这里只认键、不认容器:\n * - `POST /api/auth/login` → `LoginResponse.user`(Map,含 id/username/tenantId/roles)\n * - `GET /api/auth/info` → `UserInfoResponse`(含 userId/username/tenantId/roles)\n *\n * 全部字段都当可选处理:任何一个缺失都不该让登录失败——登录已经成功了,\n * 这里只是回显。\n */\nexport function toPrincipal(raw: unknown): Principal | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const o = raw as Record<string, unknown>;\n const principal: Principal = {};\n // tenantId 可能是 number(旧租户 id=1) 或 string(UUID),统一成 string 展示。\n if (o.tenantId !== undefined && o.tenantId !== null) principal.tenantId = String(o.tenantId);\n if (typeof o.username === 'string' && o.username) principal.username = o.username;\n if (Array.isArray(o.roles)) principal.roles = o.roles.map(String);\n return Object.keys(principal).length > 0 ? principal : undefined;\n}\n\n/** 回显用的一行摘要。`principal` 为空时明说「服务端未回身份」,不静默略过。 */\nexport function describePrincipal(principal: Principal | undefined): string {\n if (!principal) return 'server-identity=<未回身份>';\n const parts: string[] = [];\n parts.push(`tenant=${principal.tenantId ?? '<未回>'}`);\n parts.push(`user=${principal.username ?? '<未回>'}`);\n if (principal.roles?.length) parts.push(`roles=${principal.roles.join(',')}`);\n return parts.join(' ');\n}\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, type TokenRecord } from './token-store';\nimport { toPrincipal, type LoginOutcome } from './principal';\n\nexport interface PasswordLoginInput {\n endpoint: string;\n tenantId: string;\n username: string;\n password: string;\n profile: string;\n httpClient: AxiosInstance;\n}\n\n/** 后端 `SecurityProperties.token.expiration` 的缺省值(24h),仅在服务端没回 expiresIn 时兜底。 */\nexport const DEFAULT_SESSION_TTL_SECONDS = 24 * 60 * 60;\n\nexport async function loginPassword(input: PasswordLoginInput): Promise<LoginOutcome> {\n const resp = await input.httpClient.post(\n `${input.endpoint}/api/auth/login`,\n {\n tenantId: input.tenantId,\n username: input.username,\n password: input.password,\n },\n { headers: { 'Content-Type': 'application/json' } },\n );\n const { token, expiresIn, user } = resp.data;\n if (!token) {\n throw new Error('Login response missing token');\n }\n\n const ttlSeconds = expiresIn ?? DEFAULT_SESSION_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();\n\n const store = new TokenStore(input.profile);\n const accessTokenRef = await store.setAccessToken(token);\n\n const record: TokenRecord = {\n scheme: 'password',\n accessTokenRef,\n // 口令会话**没有** refresh token —— 后端 `/api/auth/login` 只回 token。\n // 它的续期语义是「同一个 token 被服务端重设 TTL」,见 refresh.ts。\n refreshTokenRef: undefined,\n expiresAt,\n scopes: ['*'],\n ttlSeconds,\n };\n await store.writeMeta(record);\n return { record, principal: toPrincipal(user) };\n}\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, isServerSlidingSession, type TokenRecord } from './token-store';\nimport { DEFAULT_SESSION_TTL_SECONDS } from './password';\n\nexport interface RefreshInput {\n endpoint: string;\n /**\n * Identity name (preferred). When set, uses TokenStore.forIdentity(identity).\n * Required for sessions established via HcmClient.fromAuthContext.\n */\n identity?: string;\n /**\n * @deprecated Profile name (legacy). Used when identity is not provided —\n * for backward compat with fromConfig callers and tests pre-T15.\n */\n profile?: string;\n httpClient: AxiosInstance;\n}\n\n/**\n * 本地在「还剩这么多」时就主动去续期。\n *\n * 🔴 这个值**不需要、也不应该**去对齐服务端的 `SecurityProperties.token.refreshThreshold`\n * (缺省 2h):那是服务端**自动**续期的触发线,而我们打的 `/api/auth/refresh` 是\n * **无条件**续期(只要 Redis key 还在就重设 TTL),跟阈值无关。抄一份服务端常量到这里\n * 只会多一个会漂的载体。\n */\nexport const PROACTIVE_REFRESH_MARGIN_SECONDS = 60 * 60;\n\n/**\n * 续期。**三种 scheme 是三套完全不同的服务端语义,不是一个端点能覆盖的。**\n *\n * 🔴 此前这里写成了一条,于是三条全错,而且错得不会被发现——口令会话在\n * 「取 refresh token」那一步就抛了,请求根本没发出去,后面那段对不上的协议\n * 因此永远跑不到,测试也只覆盖了这些提前抛出的错误路径。三条的真实形态:\n *\n * - **password**:`POST /api/auth/refresh`,认 **Authorization 头**,服务端把\n * 同一个 token 的 Redis TTL 重设回 expiration,响应体只有\n * `{\"message\":\"token刷新成功\"}`——**没有 access_token**。旧实现往 body 塞\n * `{refresh_token}` 再读 `resp.data.access_token`,两头都对不上;而口令登录\n * 压根拿不到 refresh token,连这个请求都发不出去。\n * - **client_credentials**:走 `POST /api/oauth/token` + `grant_type=refresh_token`,\n * 且后端在进 switch 之前就强制校验 `client_id` + `client_secret`。本 CLI\n * **不保存 client_secret**(有意为之:那是长期凭据) ⇒ 这条路在当前设计下\n * 不可能执行成功。所以这里**明说要重登**,而不是发一个注定 400 的请求\n * 再把它包装成一句看不懂的错误。\n * - **pat**:本来就不可续期,由后端管失效。\n */\nexport async function refreshToken(input: RefreshInput): Promise<TokenRecord> {\n if (!input.identity && !input.profile) {\n throw new Error('refreshToken: either identity or profile required');\n }\n const store = input.identity\n ? TokenStore.forIdentity(input.identity)\n : new TokenStore(input.profile!);\n const meta = await store.readMeta();\n if (!meta) throw new Error('no credentials to refresh');\n\n switch (meta.scheme) {\n case 'pat':\n throw new Error('PAT cannot be refreshed; re-run `hcm login --pat-stdin`');\n case 'client_credentials':\n throw new Error(\n 'client_credentials 会话无法自动续期:刷新需要 client_secret,而 CLI 不保存它。' +\n '重新执行 `hcm login --client-id <id>`',\n );\n case 'password':\n case 'pairing':\n // 配对码换来的就是 TokenManager 铸的普通会话 token,与口令登录**同一种东西**,\n // 续期语义完全一致(ADR-308)。\n return slidePasswordSession(store, meta, input);\n default: {\n // scheme 是联合类型,走到这里说明磁盘上的记录来自更新版本的 CLI。\n const unknown: never = meta.scheme;\n throw new Error(`unknown auth scheme in stored credentials: ${String(unknown)}`);\n }\n }\n}\n\n/**\n * 口令会话续期:打一次 `/api/auth/refresh`,成功后把本地过期时间往后推 ttlSeconds。\n *\n * 🔴 `__skipAuthRetry` 不是可选的防御性代码:`attachRetry` 在 401 上会调\n * `onRefresh()`,而 onRefresh 最终就走到这里。如果这个请求自己也 401(token 真的\n * 死了),它的 config 是**新的**、没有 `__retried` 标记,于是又触发一次 onRefresh —— \n * 每一层都发一个新请求,**无界递归**。此前这条路被提前抛出的错误挡着从没跑到过,\n * 一旦让它真发请求,递归就成立了。\n */\nasync function slidePasswordSession(\n store: TokenStore,\n meta: TokenRecord,\n input: RefreshInput,\n): Promise<TokenRecord> {\n const accessToken = await store.getAccessToken();\n if (!accessToken) {\n throw new Error('no access token to refresh; re-run `hcm login`');\n }\n\n await input.httpClient.post(`${input.endpoint}/api/auth/refresh`, undefined, {\n headers: { Authorization: `Bearer ${accessToken}` },\n // 见上:阻断 401 → onRefresh → 本函数 → 401 的递归。\n __skipAuthRetry: true,\n });\n\n // 服务端不回新的过期时间,重设的 TTL 就是签发时那个值。\n const ttlSeconds = meta.ttlSeconds ?? DEFAULT_SESSION_TTL_SECONDS;\n const record: TokenRecord = {\n ...meta,\n // token 本身没变,accessTokenRef / scopes 原样保留。\n expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),\n ttlSeconds,\n };\n await store.writeMeta(record);\n return record;\n}\n\nexport function needsRefresh(meta: TokenRecord, marginSec = 60): boolean {\n const expiresAtMs = new Date(meta.expiresAt).getTime();\n return Date.now() + marginSec * 1000 > expiresAtMs;\n}\n\n/**\n * 命令开跑前把会话续上——**这才是\"登录一次基本不用再输密码\"落地的那一处**。\n *\n * 背景:后端 `TokenAuthenticationFilter` 在每个带 token 的请求上都会滑动续期\n * (剩余 <= refreshThreshold 时把 Redis TTL 重设回 24h),也就是说**只要你在用,\n * 服务端的会话就不会死**。而 CLI 这边的 `expiresAt` 是登录那一刻算死的静态值、\n * 之后再没更新过,`ensureActiveAuth` 拿它一比就抛 TOKEN_EXPIRED —— 结果是\n * **CLI 在拒绝服务端明明还认的 token**。\n *\n * 这里做的事:本地剩余时间进入窗口时主动打一次无条件续期端点,并把服务端那次\n * 续期的结果**写回本地**,让本地的过期判断重新变成真的。\n *\n * 返回是否真的续了。**任何失败都不抛**:续期是尽力而为,失败时后面的请求自己会\n * 401 并给出该给的引导;为了一次续期失败就让整条命令挂掉是本末倒置。\n */\nexport async function ensureSessionFresh(input: RefreshInput): Promise<boolean> {\n try {\n const store = input.identity\n ? TokenStore.forIdentity(input.identity)\n : new TokenStore(input.profile!);\n const meta = await store.readMeta();\n // 只有服务端会话 token 是「服务端滑动同一个 token」这套语义。PAT 由后端管失效,\n // client_credentials 拿不到 secret 续不了——两者在这里都该原样放过。\n // 🔴 走谓词而不是 `=== 'password'`:见 isServerSlidingSession 的说明。\n if (!meta || !isServerSlidingSession(meta.scheme)) return false;\n if (!needsRefresh(meta, PROACTIVE_REFRESH_MARGIN_SECONDS)) return false;\n await refreshToken(input);\n return true;\n } catch {\n return false;\n }\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport yaml from 'js-yaml';\nimport { hcmConfigDir, profileDir, profileFile } from './paths';\n\nexport interface ProfileConfig {\n endpoint: string;\n tenantId: string;\n defaultAgent?: string;\n displayName?: string;\n}\n\nexport async function loadProfile(profile: string): Promise<ProfileConfig> {\n const file = profileFile(profile);\n let content: string;\n try {\n content = await fs.readFile(file, 'utf-8');\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`profile '${profile}' not found at ${file}`);\n }\n throw e;\n }\n return yaml.load(content) as ProfileConfig;\n}\n\nexport async function saveProfile(profile: string, cfg: ProfileConfig): Promise<void> {\n await fs.mkdir(profileDir(profile), { recursive: true });\n await fs.writeFile(profileFile(profile), yaml.dump(cfg), 'utf-8');\n}\n\nexport async function listProfiles(): Promise<string[]> {\n const root = hcmConfigDir();\n try {\n const entries = await fs.readdir(root, { withFileTypes: true });\n const result: string[] = [];\n for (const e of entries) {\n if (e.isDirectory()) {\n try {\n await fs.access(path.join(root, e.name, 'profile.yml'));\n result.push(e.name);\n } catch {\n /* skip */\n }\n }\n }\n return result;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw e;\n }\n}\n","import * as fs from 'node:fs/promises';\nimport yaml from 'js-yaml';\nimport { globalConfigFile, hcmConfigDir } from './paths';\n\nexport interface GlobalConfig {\n /** Canonical: name of the active environment. */\n activeEnv: string;\n /**\n * @deprecated Use `activeEnv`. Kept in sync with `activeEnv` for backward compat\n * while in-tree callers migrate. Will be removed at end of Phase 2.\n */\n activeProfile?: string;\n defaults?: {\n output?: 'table' | 'json' | 'yaml';\n timeout?: string;\n };\n}\n\ninterface RawShape {\n activeProfile?: string;\n activeEnv?: string;\n defaults?: GlobalConfig['defaults'];\n}\n\nconst DEFAULT_ENV = 'default';\nconst DEFAULT_DEFAULTS = { output: 'table' as const, timeout: '60s' };\n\nexport async function loadGlobalConfig(): Promise<GlobalConfig> {\n let raw: RawShape = {};\n try {\n const content = await fs.readFile(globalConfigFile(), 'utf-8');\n raw = (yaml.load(content) as RawShape) ?? {};\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e;\n }\n const activeEnv = raw.activeEnv ?? raw.activeProfile ?? DEFAULT_ENV;\n return {\n activeEnv,\n activeProfile: activeEnv, // dual-write alias\n defaults: { ...DEFAULT_DEFAULTS, ...raw.defaults },\n };\n}\n\nexport async function saveGlobalConfig(cfg: GlobalConfig): Promise<void> {\n await fs.mkdir(hcmConfigDir(), { recursive: true });\n // Canonical wins. Legacy callers that still mutate `activeProfile` must be\n // migrated to assign `activeEnv` instead — done in T5 for the 3 mutation\n // sites (config.ts × 2, login.ts × 1). Read-only callers stay safe via the\n // dual-write alias from `loadGlobalConfig`.\n const activeEnv = cfg.activeEnv ?? cfg.activeProfile ?? DEFAULT_ENV;\n const out = {\n activeEnv,\n activeProfile: activeEnv,\n defaults: cfg.defaults,\n };\n await fs.writeFile(globalConfigFile(), yaml.dump(out), 'utf-8');\n}\n\n/**\n * Resolve active env name. Priority: --env flag > HCM_ENV > HCM_PROFILE (legacy) > global.activeEnv\n */\nexport function resolveActiveEnv(\n args: { envFlag?: string },\n env: NodeJS.ProcessEnv,\n global: GlobalConfig,\n): string {\n return args.envFlag ?? env.HCM_ENV ?? env.HCM_PROFILE ?? global.activeEnv;\n}\n\n/**\n * @deprecated Use `resolveActiveEnv`. Keeps old signature `(args: {flag})` for backward compat.\n * Will be removed at end of Phase 2.\n */\nexport function resolveActiveProfile(\n args: { flag?: string },\n env: NodeJS.ProcessEnv,\n global: GlobalConfig,\n): string {\n return resolveActiveEnv({ envFlag: args.flag }, env, global);\n}\n","import * as fs from 'node:fs/promises';\nimport yaml from 'js-yaml';\nimport { envDir, envFile, envsDir } from './paths';\nimport { CliError, CliErrorCode } from '../errors';\n\nexport interface EnvConfig {\n endpoint: string;\n frontend?: string;\n tenantId?: string;\n defaultIdentity?: string;\n defaultAgent?: string;\n}\n\nexport async function saveEnv(name: string, cfg: EnvConfig): Promise<void> {\n if (!cfg.endpoint) {\n throw new CliError({\n code: CliErrorCode.ENV_ENDPOINT_REQUIRED,\n message: `env '${name}' requires endpoint`,\n });\n }\n await fs.mkdir(envDir(name), { recursive: true });\n await fs.writeFile(envFile(name), yaml.dump(cfg), 'utf-8');\n}\n\nexport async function loadEnv(name: string): Promise<EnvConfig> {\n try {\n const content = await fs.readFile(envFile(name), 'utf-8');\n return yaml.load(content) as EnvConfig;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new CliError({\n code: CliErrorCode.ENV_NOT_FOUND,\n message: `env '${name}' not found. Run: hcm env list`,\n });\n }\n throw e;\n }\n}\n\nexport async function listEnvs(): Promise<string[]> {\n try {\n const entries = await fs.readdir(envsDir(), { withFileTypes: true });\n const result: string[] = [];\n for (const e of entries) {\n if (!e.isDirectory()) continue;\n try {\n await fs.access(envFile(e.name));\n result.push(e.name);\n } catch {\n /* skip */\n }\n }\n return result;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw e;\n }\n}\n\nexport async function deleteEnv(name: string): Promise<void> {\n try {\n await fs.access(envFile(name));\n } catch {\n throw new CliError({\n code: CliErrorCode.ENV_NOT_FOUND,\n message: `env '${name}' not found`,\n });\n }\n await fs.rm(envDir(name), { recursive: true, force: true });\n}\n","export enum ExitCode {\n SUCCESS = 0,\n USAGE_ERROR = 1,\n AUTH_ERROR = 2,\n BUSINESS_ERROR = 3,\n NETWORK_ERROR = 4,\n CONFIG_ERROR = 5,\n SIGINT = 130,\n}\n\nexport enum CliErrorCode {\n // usage (exit 1)\n INVALID_ARGUMENT = 'INVALID_ARGUMENT',\n INVALID_JSON = 'INVALID_JSON',\n MISSING_FLAG = 'MISSING_FLAG',\n\n // auth (exit 2)\n AUTH_FAILED = 'AUTH_FAILED',\n AUTH_EXPIRED = 'AUTH_EXPIRED',\n AUTH_REFRESH_FAILED = 'AUTH_REFRESH_FAILED',\n WS_AUTH_ERROR = 'WS_AUTH_ERROR',\n SCOPE_FORBIDDEN = 'SCOPE_FORBIDDEN',\n\n // business (exit 3)\n BUSINESS_RULE_VIOLATION = 'BUSINESS_RULE_VIOLATION',\n MODEL_NOT_FOUND = 'MODEL_NOT_FOUND',\n ACTION_NOT_FOUND = 'ACTION_NOT_FOUND',\n AGENT_NOT_FOUND = 'AGENT_NOT_FOUND',\n TOOL_TIMEOUT = 'TOOL_TIMEOUT',\n\n // network (exit 4)\n NETWORK_ERROR = 'NETWORK_ERROR',\n TIMEOUT = 'TIMEOUT',\n SERVER_ERROR = 'SERVER_ERROR',\n PROTOCOL_UNSUPPORTED = 'PROTOCOL_UNSUPPORTED',\n\n // config (exit 5)\n PROFILE_NOT_FOUND = 'PROFILE_NOT_FOUND',\n CONFIG_PARSE_ERROR = 'CONFIG_PARSE_ERROR',\n KEYCHAIN_UNAVAILABLE = 'KEYCHAIN_UNAVAILABLE',\n\n // env + identity (exit 5/2)\n ENV_NOT_FOUND = 'ENV_NOT_FOUND',\n ENV_ALREADY_EXISTS = 'ENV_ALREADY_EXISTS',\n ENV_ENDPOINT_REQUIRED = 'ENV_ENDPOINT_REQUIRED',\n IDENTITY_NOT_FOUND = 'IDENTITY_NOT_FOUND',\n IDENTITY_ALREADY_EXISTS = 'IDENTITY_ALREADY_EXISTS',\n IDENTITY_NOT_IN_ENV = 'IDENTITY_NOT_IN_ENV',\n TOKEN_EXPIRED = 'TOKEN_EXPIRED',\n NO_ACTIVE_ENV = 'NO_ACTIVE_ENV',\n\n // skills(exit 1/3/4)\n /** 同一个 id 在 CLI 自带与睿戎目录里同时存在。确定性拒绝,不静默取其一。 */\n SKILL_ID_CONFLICT = 'SKILL_ID_CONFLICT',\n /** 装在这份 CLI 里的技能资源读不到(dist/skills 缺失或损坏)。安装损坏,硬失败。 */\n SKILL_SOURCE_UNREACHABLE = 'SKILL_SOURCE_UNREACHABLE',\n /** `skills sync` 的锁文件此刻只记得下睿戎来源的出处;自带技能没有它要的三个字段。 */\n SYNC_SOURCE_UNSUPPORTED = 'SYNC_SOURCE_UNSUPPORTED',\n}\n\nconst CODE_TO_EXIT: Record<CliErrorCode, ExitCode> = {\n [CliErrorCode.INVALID_ARGUMENT]: ExitCode.USAGE_ERROR,\n [CliErrorCode.INVALID_JSON]: ExitCode.USAGE_ERROR,\n [CliErrorCode.MISSING_FLAG]: ExitCode.USAGE_ERROR,\n [CliErrorCode.AUTH_FAILED]: ExitCode.AUTH_ERROR,\n [CliErrorCode.AUTH_EXPIRED]: ExitCode.AUTH_ERROR,\n [CliErrorCode.AUTH_REFRESH_FAILED]: ExitCode.AUTH_ERROR,\n [CliErrorCode.WS_AUTH_ERROR]: ExitCode.AUTH_ERROR,\n [CliErrorCode.SCOPE_FORBIDDEN]: ExitCode.AUTH_ERROR,\n [CliErrorCode.BUSINESS_RULE_VIOLATION]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.MODEL_NOT_FOUND]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.ACTION_NOT_FOUND]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.AGENT_NOT_FOUND]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.TOOL_TIMEOUT]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.NETWORK_ERROR]: ExitCode.NETWORK_ERROR,\n [CliErrorCode.TIMEOUT]: ExitCode.NETWORK_ERROR,\n [CliErrorCode.SERVER_ERROR]: ExitCode.NETWORK_ERROR,\n [CliErrorCode.PROTOCOL_UNSUPPORTED]: ExitCode.NETWORK_ERROR,\n [CliErrorCode.PROFILE_NOT_FOUND]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.CONFIG_PARSE_ERROR]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.KEYCHAIN_UNAVAILABLE]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.ENV_NOT_FOUND]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.ENV_ALREADY_EXISTS]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.ENV_ENDPOINT_REQUIRED]: ExitCode.USAGE_ERROR,\n [CliErrorCode.IDENTITY_NOT_FOUND]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.IDENTITY_ALREADY_EXISTS]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.IDENTITY_NOT_IN_ENV]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.TOKEN_EXPIRED]: ExitCode.AUTH_ERROR,\n [CliErrorCode.NO_ACTIVE_ENV]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.SKILL_ID_CONFLICT]: ExitCode.BUSINESS_ERROR,\n // 🔴 装坏了不是网络问题:报 network 会让顾问先去查 VPN。\n [CliErrorCode.SKILL_SOURCE_UNREACHABLE]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.SYNC_SOURCE_UNSUPPORTED]: ExitCode.USAGE_ERROR,\n};\n\n/**\n * 🔴 **兜底不是防御性编程,是防「静默退 0」**。\n *\n * `CODE_TO_EXIT` 是 `Record<CliErrorCode, ExitCode>`,编译期漏一条会红,\n * 所以这一句在类型正确的构建里永远走不到。它防的是**构建不同步**:\n * CLI 的 dist 认识一个新错误码、而 sdk 的 dist 还是旧的时候,\n * 这里会取到 `undefined` ⇒ `process.exit(undefined)` ⇒ **报了错却退 0**。\n * 实测撞到过一次(只跑了 `npm run build -w @hcmai/cli`,没重建 sdk):\n * 终端上明明打印了 Error,脚本里 `$?` 是 0——上游调用方会当成成功。\n */\nexport function exitCodeFor(code: CliErrorCode): ExitCode {\n return CODE_TO_EXIT[code] ?? ExitCode.USAGE_ERROR;\n}\n","import { CliErrorCode, exitCodeFor } from './codes';\nimport type { ExitCode } from './codes';\n\nexport interface CliErrorInit {\n code: CliErrorCode;\n message: string;\n context?: Record<string, unknown>;\n traceId?: string;\n profile?: string;\n cause?: unknown;\n}\n\nexport class CliError extends Error {\n readonly code: CliErrorCode;\n readonly exitCode: ExitCode;\n readonly context?: Record<string, unknown>;\n readonly traceId?: string;\n readonly profile?: string;\n override readonly cause?: unknown;\n\n constructor(init: CliErrorInit) {\n super(init.message);\n this.name = 'CliError';\n this.code = init.code;\n this.exitCode = exitCodeFor(init.code);\n this.context = init.context;\n this.traceId = init.traceId;\n this.profile = init.profile;\n this.cause = init.cause;\n }\n\n toFormattedString(): string {\n const parts: string[] = [`${this.code}: ${this.message}`];\n // CliErrorCode 是命令行自己的分类(决定 exit code),后端错误码比它精确得多\n // (FILTER_SYNTAX_ERROR vs 一律降级成的 BUSINESS_RULE_VIOLATION)。两者不一致时都要显示,\n // 只显示前者就是在报一个错的错误码。\n const serverCode = this.context?.responseCode;\n if (typeof serverCode === 'string' && serverCode && serverCode !== this.code) {\n parts.push(`[服务端错误码: ${serverCode}]`);\n }\n const field = this.context?.field;\n if (typeof field === 'string' && field) parts.push(`[字段: ${field}]`);\n if (this.traceId) parts.push(`[traceId: ${this.traceId}]`);\n if (this.profile) parts.push(`[profile: ${this.profile}]`);\n let out = parts.join(' ');\n // 🔴 details 必须显示。后端把 BusinessException 的 description 放进 details\n // (CommonErrorResponse:data 为空时 details 就是 description),那句话往往是**唯一**\n // 说清「哪里错了、该改成什么」的文本,而 message 常常是错误码的静态措辞。\n // fromAxiosError 早就把它解析进 context 了,却一直没渲染出来——2026-08-29 顾问仿真\n // 实测:静态 JSON 数据源传了标量,服务端回的是「json 数据源 data 字段必须是 List 或\n // Map」,命令行只显示 BUSINESS_RULE_VIOLATION,Agent 无从自纠,整轮验收被迫中止。\n const details = this.context?.details;\n const detailText = formatDetails(details);\n if (detailText) out += `\\n 详情: ${detailText}`;\n const nextStep = this.context?.nextStep;\n if (typeof nextStep === 'string' && nextStep) out += `\\n 下一步: ${nextStep}`;\n return out;\n }\n\n toJSON() {\n return {\n code: this.code,\n message: this.message,\n context: this.context,\n traceId: this.traceId,\n };\n }\n}\n\n// axios doesn't export a clean error type usable here; intentional any\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function fromAxiosError(err: any, profile?: string): CliError {\n const status = err.response?.status;\n const traceId = err.response?.headers?.['x-trace-id'];\n\n // 🔴 HCM 的错误信封是嵌套的:{ \"error\": { code, message, field, details, nextStep, retryable } }\n // —— 见后端 CommonErrorResponse / ActionError。以前这里读的是顶层 data.code / data.message,\n // 对真实响应**恒取不到**:消息落回 axios 的 \"Request failed with status code 400\",\n // 错误码一律按 HTTP 状态码降级,连带下面 MODEL_NOT_FOUND / ACTION_NOT_FOUND 两条特判成了死代码。\n // 单测当时全绿,因为它们喂的是平铺形状 —— 围着 bug 建的 fixture 证明不了任何事。\n // 顶层留作回退:少数早期端点确实平铺返回。\n const data = err.response?.data;\n const body = (data?.error ?? data) as Record<string, unknown> | undefined;\n const respCode = body?.code as string | undefined;\n const respMsg = body?.message as string | undefined;\n const respField = body?.field as string | undefined;\n const respDetails = body?.details;\n const respNextStep = body?.nextStep as string | undefined;\n const respRetryable = body?.retryable as boolean | undefined;\n\n let code: CliErrorCode;\n if (status === 401) code = CliErrorCode.AUTH_FAILED;\n else if (status === 403) code = CliErrorCode.SCOPE_FORBIDDEN;\n else if (status === 404 && respCode === 'MODEL_NOT_FOUND') code = CliErrorCode.MODEL_NOT_FOUND;\n else if (status === 404 && respCode === 'ACTION_NOT_FOUND') code = CliErrorCode.ACTION_NOT_FOUND;\n else if (status && status >= 400 && status < 500) code = CliErrorCode.BUSINESS_RULE_VIOLATION;\n else if (status && status >= 500) code = CliErrorCode.SERVER_ERROR;\n else if (err.code === 'ECONNABORTED') code = CliErrorCode.TIMEOUT;\n else code = CliErrorCode.NETWORK_ERROR;\n\n return new CliError({\n code,\n message: respMsg ?? err.message ?? 'request failed',\n context: {\n status,\n responseCode: respCode,\n // 结构化错误的这几件必须原样带出:field 指哪个字段错了、details 机器可读、\n // nextStep 是后端给的路标(ADR-166 终局协议)—— 吞掉它等于让命令行用户自己猜下一步。\n ...(respField ? { field: respField } : {}),\n ...(respDetails !== undefined ? { details: respDetails } : {}),\n ...(respNextStep ? { nextStep: respNextStep } : {}),\n ...(respRetryable !== undefined ? { retryable: respRetryable } : {}),\n },\n traceId,\n profile,\n cause: err,\n });\n}\n\n\n/**\n * details 可能是字符串(后端 description 回退路径),也可能是结构化 payload。\n * 字符串原样显示;结构化的紧凑序列化,超长截断——宁可截断也不要整段不显示。\n */\nfunction formatDetails(details: unknown): string | undefined {\n if (details === undefined || details === null) return undefined;\n if (typeof details === 'string') return details.trim() || undefined;\n if (typeof details === 'number' || typeof details === 'boolean') return String(details);\n let text: string;\n try {\n text = JSON.stringify(details);\n } catch {\n return undefined;\n }\n if (!text || text === '{}' || text === '[]') return undefined;\n return text.length > 800 ? `${text.slice(0, 800)}…(已截断)` : text;\n}\n","import * as fs from 'node:fs/promises';\nimport { identityDir, identityMetaFile, identitiesDir, envFile } from './paths';\nimport { CliError, CliErrorCode } from '../errors';\nimport type { AuthScheme } from '../auth/token-store';\n\nexport interface IdentityMeta {\n env: string;\n scheme: AuthScheme;\n scopes: string[];\n expiresAt?: string;\n username?: string;\n /**\n * keychain ref same as TokenRecord.accessTokenRef. Populated by TokenStore.forIdentity.\n * Optional here because saveIdentity may be called before token is written.\n */\n accessTokenRef?: string;\n refreshTokenRef?: string;\n /**\n * **服务端认定的租户**(登录响应里的 `user.tenantId` / `/api/auth/info` 的 tenantId),\n * 不是命令行上传进去的那个。两者不一致 = 你在对着另一个租户干活,而一切看起来都正常。\n *\n * 命令执行时优先用它,`EnvConfig.tenantId` 只是登录前的入参与兜底 —— 后者缺省是\n * 写死的 `'1'`,而 ADR-177 之后新租户是 UUID,那个缺省几乎必然是错的。\n */\n tenantId?: string;\n /**\n * 见 `TokenRecord.ttlSeconds`。\n *\n * 🔴 必须在这里也留一份:`identities/<name>/credentials.json` 这**一个文件**有两个\n * 写入方——`TokenStore.forIdentity().writeMeta()` 写 TokenRecord 形状,`saveIdentity()`\n * 写 IdentityMeta 形状,后者最后写、且只写自己声明的字段。不在这里声明,\n * 登录流程末尾这一次 saveIdentity 就会把 ttlSeconds 抹掉,续期时只能去猜 TTL。\n */\n ttlSeconds?: number;\n}\n\nasync function assertEnvExists(env: string): Promise<void> {\n try {\n await fs.access(envFile(env));\n } catch {\n throw new CliError({\n code: CliErrorCode.IDENTITY_NOT_IN_ENV,\n message: `env '${env}' does not exist. Run: hcm env add ${env} --endpoint <url>`,\n });\n }\n}\n\nexport async function saveIdentity(name: string, meta: IdentityMeta): Promise<void> {\n await assertEnvExists(meta.env);\n await fs.mkdir(identityDir(name), { recursive: true });\n await fs.writeFile(identityMetaFile(name), JSON.stringify(meta, null, 2), { mode: 0o600 });\n}\n\nexport async function loadIdentity(name: string): Promise<IdentityMeta> {\n try {\n const raw = await fs.readFile(identityMetaFile(name), 'utf-8');\n return JSON.parse(raw) as IdentityMeta;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new CliError({\n code: CliErrorCode.IDENTITY_NOT_FOUND,\n message: `identity '${name}' not found. Run: hcm identity list`,\n });\n }\n throw e;\n }\n}\n\nexport async function listIdentities(env?: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(identitiesDir(), { withFileTypes: true });\n const result: string[] = [];\n for (const e of entries) {\n if (!e.isDirectory()) continue;\n try {\n const meta = await loadIdentity(e.name);\n if (!env || meta.env === env) result.push(e.name);\n } catch {\n /* skip broken */\n }\n }\n return result;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw e;\n }\n}\n\nexport async function deleteIdentity(name: string): Promise<void> {\n try {\n await fs.access(identityMetaFile(name));\n } catch {\n throw new CliError({\n code: CliErrorCode.IDENTITY_NOT_FOUND,\n message: `identity '${name}' not found`,\n });\n }\n await fs.rm(identityDir(name), { recursive: true, force: true });\n}\n","import * as fs from 'node:fs/promises';\nimport type { Dirent } from 'node:fs';\nimport * as path from 'node:path';\nimport yaml from 'js-yaml';\nimport {\n hcmConfigDir,\n profileDir,\n profileFile,\n credentialsFile,\n archiveDir,\n envFile,\n} from './paths';\nimport { saveEnv, deleteEnv } from './env';\nimport { saveIdentity, deleteIdentity } from './identity';\nimport { loadGlobalConfig, saveGlobalConfig } from './global-config';\n\ninterface LegacyProfile {\n endpoint: string;\n tenantId?: string;\n defaultAgent?: string;\n}\n\ninterface LegacyCredentials {\n scheme: 'password' | 'client_credentials' | 'pat';\n accessTokenRef: string;\n refreshTokenRef?: string;\n expiresAt: string;\n scopes: string[];\n username?: string;\n}\n\nexport interface MigrationSummary {\n migrated: string[];\n failed: string[];\n skipped: string[];\n}\n\nconst RESERVED = new Set(['envs', 'identities', '_archive']);\n\n/**\n * One-shot lazy migration from legacy `<hcm>/<profile>/profile.yml + credentials.json`\n * to canonical `<hcm>/envs/<name>/env.yml + identities/<name>/credentials.json`.\n *\n * Semantics:\n * - Idempotent: profiles already migrated (env.yml exists) are skipped.\n * - Per-profile atomicity: any failure inside migrateOne rolls back the partial env.\n * - Legacy files for failed profiles stay in place so the user can inspect / retry.\n * - On any migration success, the global config.yml is rewritten so activeEnv is\n * the canonical key (loadGlobalConfig already provides fallback read of\n * legacy activeProfile).\n * - Structured stderr log: `[hcm] event=migration_done count=N archive=PATH/`\n * and `[hcm] event=migration_failed profile=NAME reason=\"...\"`.\n */\nexport async function migrateLegacyProfiles(): Promise<MigrationSummary> {\n const summary: MigrationSummary = { migrated: [], failed: [], skipped: [] };\n const root = hcmConfigDir();\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(root, { withFileTypes: true });\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return summary;\n throw e;\n }\n\n for (const e of entries) {\n if (!e.isDirectory() || RESERVED.has(e.name)) continue;\n const legacyYml = profileFile(e.name);\n try {\n await fs.access(legacyYml);\n } catch {\n continue; // not a legacy profile dir\n }\n\n // already migrated?\n try {\n await fs.access(envFile(e.name));\n summary.skipped.push(e.name);\n continue;\n } catch {\n /* not yet migrated */\n }\n\n let result: { identityName: string } | null = null;\n try {\n result = await migrateOne(e.name);\n summary.migrated.push(e.name);\n } catch (err) {\n summary.failed.push(e.name);\n // Rollback: env AND identity (identity may exist if archive step failed)\n try {\n await deleteEnv(e.name);\n } catch {\n /* env was not yet created — nothing to roll back */\n }\n if (result === null) {\n // migrateOne threw before returning identityName; try to clean up by\n // computed name using the same inference migrateOne would have used.\n try {\n const creds = JSON.parse(\n await fs.readFile(credentialsFile(e.name), 'utf-8'),\n ) as { username?: string };\n const guessedId = creds?.username ? `${creds.username}@${e.name}` : `default@${e.name}`;\n await deleteIdentity(guessedId);\n } catch {\n /* identity not created or creds unreadable — fine */\n }\n } else {\n try {\n await deleteIdentity(result.identityName);\n } catch {\n /* not created */\n }\n }\n process.stderr.write(\n `[hcm] event=migration_failed profile=${e.name} reason=${JSON.stringify(String((err as Error).message))}\\n`,\n );\n }\n }\n\n // upgrade global config.yml: activeProfile → activeEnv (canonical key)\n if (summary.migrated.length > 0) {\n const g = await loadGlobalConfig(); // 自动兼容读 activeProfile fallback\n await saveGlobalConfig({ activeEnv: g.activeEnv, defaults: g.defaults });\n\n const archive = archiveDir();\n process.stderr.write(\n `[hcm] event=migration_done count=${summary.migrated.length} archive=${archive}/\\n`,\n );\n }\n\n return summary;\n}\n\nasync function migrateOne(name: string): Promise<{ identityName: string }> {\n const legacyProfile = yaml.load(\n await fs.readFile(profileFile(name), 'utf-8'),\n ) as LegacyProfile | null;\n if (!legacyProfile?.endpoint) {\n throw new Error('legacy profile.yml missing endpoint');\n }\n\n // Read credentials.json eagerly so a corrupt file fails BEFORE we touch env.yml.\n // (This way the rollback branch is exercised cleanly and the legacy files stay\n // in place for inspection — matches the \"partial fail\" test contract.)\n let creds: LegacyCredentials | null = null;\n try {\n creds = JSON.parse(\n await fs.readFile(credentialsFile(name), 'utf-8'),\n ) as LegacyCredentials;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new Error(`credentials.json unreadable: ${(e as Error).message}`);\n }\n // ENOENT → no credentials, that's fine\n }\n\n const identityName = creds?.username ? `${creds.username}@${name}` : `default@${name}`;\n\n // 1. write env.yml (with defaultIdentity pointer)\n await saveEnv(name, {\n endpoint: legacyProfile.endpoint,\n tenantId: legacyProfile.tenantId,\n defaultAgent: legacyProfile.defaultAgent,\n defaultIdentity: identityName,\n });\n\n // 2. write identity meta\n // - if creds existed: full meta carried over\n // - if creds missing: stub identity (scheme=password, empty scopes) — env's\n // defaultIdentity points here, user must `hcm login` to populate the token\n if (creds) {\n await saveIdentity(identityName, {\n env: name,\n scheme: creds.scheme,\n scopes: creds.scopes,\n expiresAt: creds.expiresAt,\n username: creds.username,\n accessTokenRef: creds.accessTokenRef,\n refreshTokenRef: creds.refreshTokenRef,\n });\n } else {\n // Stub: no credentials yet — set expiresAt in the past so auth-guard treats\n // this as \"expired, force re-login\" rather than \"valid password scheme\".\n // T13 login overwrites this with real meta when user runs `hcm login --env <name>`.\n await saveIdentity(identityName, {\n env: name,\n scheme: 'password',\n scopes: [],\n expiresAt: '1970-01-01T00:00:00Z',\n });\n }\n\n // 3. archive legacy files (only after env + identity are persisted)\n const archiveTarget = path.join(archiveDir(), name);\n await fs.mkdir(archiveTarget, { recursive: true });\n await fs.rename(profileFile(name), path.join(archiveTarget, 'profile.yml.bak'));\n try {\n await fs.rename(\n credentialsFile(name),\n path.join(archiveTarget, 'credentials.json.bak'),\n );\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e;\n }\n\n // try to remove the now-empty profile dir (may still hold non-migrated artifacts\n // like last-conversation.json or repl-history — keep dir in that case)\n try {\n await fs.rmdir(profileDir(name));\n } catch {\n /* not empty — leave dir for non-migrated artifacts */\n }\n\n return { identityName };\n}\n","import { loadEnv } from './env';\nimport { listIdentities } from './identity';\nimport { CliError, CliErrorCode } from '../errors';\n\n/**\n * Resolve active identity name.\n * Priority: --as flag > HCM_IDENTITY env var > env.defaultIdentity > first identity in env > throw.\n */\nexport async function resolveActiveIdentity(\n args: { asFlag?: string },\n env: NodeJS.ProcessEnv,\n activeEnv: string,\n): Promise<string> {\n if (args.asFlag) return args.asFlag;\n if (env.HCM_IDENTITY) return env.HCM_IDENTITY;\n const cfg = await loadEnv(activeEnv);\n if (cfg.defaultIdentity) return cfg.defaultIdentity;\n const [first] = await listIdentities(activeEnv);\n if (!first) {\n throw new CliError({\n code: CliErrorCode.IDENTITY_NOT_FOUND,\n message: `env '${activeEnv}' has no identity. Run: hcm login --env ${activeEnv}`,\n });\n }\n return first;\n}\n\n// Re-export resolveActiveEnv from global-config for symmetry\nexport { resolveActiveEnv } from './global-config';\n","import type { AxiosInstance } from 'axios';\nimport type { ModelQueryDsl, QueryResult } from './types';\n\ninterface HcmPageResponse<T> {\n content: T[];\n totalElements?: number;\n totalPages?: number;\n size?: number;\n page?: number;\n}\n\nexport async function query<T = Record<string, unknown>>(\n http: AxiosInstance,\n model: string,\n dsl: ModelQueryDsl,\n): Promise<QueryResult<T>> {\n const body: Record<string, unknown> = {};\n const filter = normalizeFilter(dsl.filter);\n if (filter !== undefined) body.filter = filter;\n const orders = normalizeOrders(dsl.sort);\n if (orders !== undefined) body.orders = orders;\n if (dsl.fields !== undefined) body.fields = dsl.fields;\n if (dsl.keyword !== undefined) body.keyword = dsl.keyword;\n\n const size = dsl.limit ?? 20;\n const page = dsl.offset ? Math.floor(dsl.offset / size) : 0;\n body.page = page;\n body.size = size;\n\n const resp = await http.post<HcmPageResponse<T>>(`/api/models/${model}/list/dsl`, body);\n return {\n rows: resp.data.content ?? [],\n total: resp.data.totalElements,\n };\n}\n\nconst OPERATOR_ALIASES: Record<string, string> = {\n $eq: 'eq',\n eq: 'eq',\n '=': 'eq',\n '==': 'eq',\n $ne: 'ne',\n ne: 'ne',\n '!=': 'ne',\n '<>': 'ne',\n $gt: 'gt',\n gt: 'gt',\n '>': 'gt',\n $gte: 'gte',\n gte: 'gte',\n '>=': 'gte',\n $lt: 'lt',\n lt: 'lt',\n '<': 'lt',\n $lte: 'lte',\n lte: 'lte',\n '<=': 'lte',\n $in: 'in',\n in: 'in',\n $nin: 'not_in',\n nin: 'not_in',\n $not_in: 'not_in',\n not_in: 'not_in',\n $like: 'like',\n like: 'like',\n $not_like: 'not_like',\n not_like: 'not_like',\n $contains: 'contains',\n contains: 'contains',\n $startswith: 'startswith',\n startswith: 'startswith',\n $endswith: 'endswith',\n endswith: 'endswith',\n $between: 'between',\n between: 'between',\n $is_null: 'is_null',\n is_null: 'is_null',\n $is_not_null: 'is_not_null',\n is_not_null: 'is_not_null',\n};\n\nfunction normalizeFilter(filter: Record<string, unknown> | undefined): unknown {\n if (filter === undefined) return undefined;\n if (!isPlainObject(filter)) return filter;\n if (isAstFilter(filter)) return filter;\n\n const leaves = Object.entries(filter).flatMap(([field, value]) => filterLeaves(field, value));\n if (leaves.length === 0) return undefined;\n return leaves.length === 1 ? leaves[0] : { and: leaves };\n}\n\nfunction isAstFilter(filter: Record<string, unknown>): boolean {\n return (\n 'and' in filter ||\n 'or' in filter ||\n 'not' in filter ||\n (typeof filter.field === 'string' && typeof filter.op === 'string')\n );\n}\n\nfunction filterLeaves(field: string, value: unknown): Array<Record<string, unknown>> {\n if (isPlainObject(value)) {\n const entries = Object.entries(value);\n if (entries.length > 0 && entries.every(([key]) => isFilterOperator(key))) {\n return entries.map(([key, operatorValue]) => {\n const op = OPERATOR_ALIASES[key];\n if (!op) {\n throw new Error(`Unsupported filter operator \"${key}\" for field \"${field}\"`);\n }\n return isUnaryOperator(op)\n ? { field, op }\n : { field, op, value: operatorValue };\n });\n }\n if (entries.some(([key]) => isFilterOperator(key))) {\n throw new Error(`Unsupported mixed filter value for field \"${field}\"`);\n }\n }\n return [{ field, op: Array.isArray(value) ? 'in' : 'eq', value }];\n}\n\nfunction isFilterOperator(key: string): boolean {\n return Object.prototype.hasOwnProperty.call(OPERATOR_ALIASES, key);\n}\n\nfunction isUnaryOperator(op: string): boolean {\n return op === 'is_null' || op === 'is_not_null';\n}\n\nfunction normalizeOrders(sort: Array<Record<string, unknown>> | undefined): unknown {\n if (sort === undefined) return undefined;\n return sort.map((item) => {\n const field = item.field;\n if (typeof field !== 'string' || field.trim() === '') {\n throw new Error('sort item requires non-empty field');\n }\n const rawDirection =\n typeof item.direction === 'string'\n ? item.direction\n : typeof item.dir === 'string'\n ? item.dir\n : typeof item.order === 'string'\n ? item.order\n : item.desc === true\n ? 'desc'\n : 'asc';\n const direction = rawDirection.toLowerCase();\n if (direction !== 'asc' && direction !== 'desc') {\n throw new Error(`sort direction must be asc or desc; got \"${rawDirection}\"`);\n }\n return { field, direction };\n });\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import type { AxiosInstance } from 'axios';\nimport type { ActionHttpMethod, ActionInput, ActionResult, ActionScope } from './types';\n\nfunction normalizeMethod(method?: ActionHttpMethod): ActionHttpMethod {\n return method ?? 'POST';\n}\n\nfunction normalizeScope(input: ActionInput): ActionScope {\n return input.scope ?? (input.id ? 'object' : 'class');\n}\n\nfunction queryParams(input: ActionInput): Record<string, unknown> {\n return {\n ...(input.params ?? {}),\n ...(input.scope === 'class' && input.id ? { id: input.id } : {}),\n ...(input.ids?.length ? { ids: input.ids.join(',') } : {}),\n };\n}\n\nfunction classBody(input: ActionInput): Record<string, unknown> {\n return {\n ...(input.params ?? {}),\n ...(input.id ? { id: input.id } : {}),\n ...(input.ids?.length ? { ids: input.ids } : {}),\n };\n}\n\nfunction objectBody(input: ActionInput): Record<string, unknown> {\n return input.params ?? {};\n}\n\nexport async function action<R = unknown>(\n http: AxiosInstance,\n model: string,\n actionName: string,\n input: ActionInput,\n): Promise<ActionResult<R>> {\n const method = normalizeMethod(input.method);\n const scope = normalizeScope(input);\n if (scope === 'object' && !input.id) {\n throw new Error(`object action ${model}.${actionName} requires id`);\n }\n\n const path = scope === 'object'\n ? `/api/models/${model}/${input.id}/action/${actionName}`\n : `/api/models/${model}/action/${actionName}`;\n\n const resp = await (async () => {\n if (method === 'GET') {\n return http.get(path, { params: queryParams(input) });\n }\n if (method === 'DELETE') {\n return http.delete(path, { params: queryParams(input) });\n }\n if (method === 'PUT') {\n return http.put(path, scope === 'object' ? objectBody(input) : classBody(input));\n }\n return http.post(path, scope === 'object' ? objectBody(input) : classBody(input));\n })();\n return resp.data as ActionResult<R>;\n}\n","import type { AxiosInstance } from 'axios';\n\nexport interface FieldMeta {\n name: string;\n type: string;\n required?: boolean;\n label?: string;\n description?: string;\n enum?: string[];\n relation?: { model: string; field?: string };\n}\n\nexport interface ActionMeta {\n name: string;\n label?: string;\n description?: string;\n params?: Array<{ name: string; type: string; required?: boolean; description?: string }>;\n scope?: string;\n httpMethod?: string;\n readOnly?: boolean;\n paramTypes?: string[];\n paramNames?: string[];\n returnType?: string;\n source?: string;\n}\n\nexport interface RelationMeta {\n key: string;\n via: string;\n label?: string;\n}\n\nexport interface ModelDescription {\n model: string;\n labels: Record<string, string>;\n fields: FieldMeta[];\n actions: ActionMeta[];\n relations: RelationMeta[];\n}\n\nexport interface DescribeOpts {\n verbose?: boolean;\n}\n\ninterface HcmFieldRaw {\n key?: string;\n name?: string;\n type?: string;\n isRequired?: boolean;\n description?: string;\n enum?: string[];\n relation?: { model: string; field?: string };\n}\n\ninterface HcmActionRaw {\n key?: string;\n name?: string;\n label?: string;\n description?: string;\n params?: Array<{ name: string; type: string; required?: boolean; description?: string }>;\n parameters?: Array<{ name: string; type: string; required?: boolean; description?: string }>;\n scope?: string;\n httpMethod?: string;\n readOnly?: boolean;\n paramTypes?: string[];\n paramNames?: string[];\n returnType?: string;\n source?: string;\n}\n\nfunction normalizeActionParams(\n action: HcmActionRaw,\n): Array<{ name: string; type: string; required?: boolean; description?: string }> | undefined {\n if (action.parameters) return action.parameters;\n if (action.params) return action.params;\n if (!action.paramNames?.length && !action.paramTypes?.length) return undefined;\n const size = Math.max(action.paramNames?.length ?? 0, action.paramTypes?.length ?? 0);\n return Array.from({ length: size }, (_, index) => ({\n name: action.paramNames?.[index] ?? `arg${index}`,\n type: action.paramTypes?.[index] ?? 'unknown',\n }));\n}\n\nexport async function describe(\n http: AxiosInstance,\n model: string,\n _opts: DescribeOpts = {},\n): Promise<ModelDescription> {\n const metaResp = await http.get(`/api/models/${model}/meta`);\n const meta = metaResp.data;\n const fields: FieldMeta[] = (meta.fields ?? []).map((f: HcmFieldRaw) => ({\n name: f.key ?? f.name ?? '',\n label: f.name,\n type: f.type ?? 'unknown',\n required: f.isRequired,\n description: f.description,\n enum: f.enum,\n relation: f.relation,\n }));\n const actions: ActionMeta[] = (meta.actions ?? []).map((a: HcmActionRaw) => ({\n name: a.key ?? a.name ?? '',\n label: a.label,\n description: a.description,\n params: normalizeActionParams(a),\n scope: a.scope,\n httpMethod: a.httpMethod,\n readOnly: a.readOnly,\n paramTypes: a.paramTypes,\n paramNames: a.paramNames,\n returnType: a.returnType,\n source: a.source,\n }));\n return {\n model: meta.name ?? model,\n labels: meta.labels ?? {},\n fields,\n actions,\n relations: meta.relations ?? [],\n };\n}\n","import type { AxiosInstance } from 'axios';\n\nexport interface CreateResult {\n id: string;\n [key: string]: unknown;\n}\n\n/** 创建一条 Model 记录,走后端 Model 层完整校验。\n * 注:返回体形态(尤其 id 字段名)见 spec §12.3,实现期对真后端核实。 */\nexport async function create(\n http: AxiosInstance,\n model: string,\n data: Record<string, unknown>,\n): Promise<CreateResult> {\n const resp = await http.post(`/api/models/${model}`, data);\n return resp.data as CreateResult;\n}\n","import type { AxiosInstance } from 'axios';\nimport type { CreateResult } from './create';\n\nexport async function update(\n http: AxiosInstance,\n model: string,\n id: string,\n data: Record<string, unknown>,\n): Promise<CreateResult> {\n const resp = await http.put(`/api/models/${model}/${id}`, data);\n return resp.data as CreateResult;\n}\n","import type { AxiosInstance } from 'axios';\n\nexport interface RemoveResult {\n id: string;\n status: number;\n data?: unknown;\n}\n\nexport async function remove(http: AxiosInstance, model: string, id: string): Promise<RemoveResult> {\n const resp = await http.delete(`/api/models/${model}/${encodeURIComponent(id)}`);\n if (resp.data && typeof resp.data === 'object') {\n return resp.data as RemoveResult;\n }\n return { id, status: resp.status };\n}\n","\n/** snake_case → camelCase(单个 key)。 */\nexport function snakeToCamel(key: string): string {\n return key.replace(/_([a-z0-9])/g, (_m, c: string) => c.toUpperCase());\n}\n\n/** 转换 record 的**顶层** key:snake_case → camelCase。\n * 嵌套对象/数组的值(jsonb / i18n)原样透传,不转内部键名(对齐 schema §4)。 */\nexport function camelizeKeys(obj: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n out[snakeToCamel(k)] = v;\n }\n return out;\n}\n","// 整字段值必须是 ${Model:key}(不嵌套、不拼接,对齐 schema §3)\nconst PLACEHOLDER_RE = /^\\$\\{([A-Za-z0-9_]+):([^}]+)\\}$/;\n\nexport class RefStore {\n private map = new Map<string, string>();\n\n private k(model: string, key: string): string {\n return `${model}:${key}`;\n }\n register(model: string, key: string, id: string): void {\n this.map.set(this.k(model, key), id);\n }\n get(model: string, key: string): string | undefined {\n return this.map.get(this.k(model, key));\n }\n snapshot(): Record<string, string> {\n return Object.fromEntries(this.map);\n }\n}\n\nexport interface ParsedPlaceholder {\n model: string;\n key: string;\n}\n\n/** 若 value 恰好是一个 ${Model:key} 占位符,返回其 model/key;否则 null。 */\nexport function parsePlaceholder(value: unknown): ParsedPlaceholder | null {\n if (typeof value !== 'string') return null;\n const m = PLACEHOLDER_RE.exec(value);\n if (!m) return null;\n const [, model, key] = m;\n if (model === undefined || key === undefined) return null;\n return { model, key };\n}\n\n/** 用 store 替换 record 顶层的 ${Model:key} 占位符。\n * 返回 { resolved, unresolved };unresolved 是 \"字段名=${Model:key}\" 列表,原值保留便于报错。 */\nexport function resolveRefs(\n record: Record<string, unknown>,\n store: RefStore,\n): { resolved: Record<string, unknown>; unresolved: string[] } {\n const resolved: Record<string, unknown> = {};\n const unresolved: string[] = [];\n for (const [field, value] of Object.entries(record)) {\n const ph = parsePlaceholder(value);\n if (!ph) {\n resolved[field] = value;\n continue;\n }\n const id = store.get(ph.model, ph.key);\n if (id === undefined) {\n unresolved.push(`${field}=\\${${ph.model}:${ph.key}}`);\n resolved[field] = value;\n } else {\n resolved[field] = id;\n }\n }\n return { resolved, unresolved };\n}\n","import yaml, { JSON_SCHEMA } from 'js-yaml';\nimport { CliError, CliErrorCode } from '../errors';\nimport type { Fixture } from './types';\n\nconst REQUIRED = ['model', 'key_field', 'count', 'items'] as const;\n\n/** 解析 + 校验单个 data fixture(对齐 spec §10 校验项 1–4)。 */\nexport function parseFixture(yamlText: string, sourcePath?: string): Fixture {\n const where = sourcePath ? ` (${sourcePath})` : '';\n const fail = (message: string): never => {\n throw new CliError({ code: CliErrorCode.INVALID_ARGUMENT, message: `import: ${message}${where}` });\n };\n\n // 注入数据视为不可信:用 JSON_SCHEMA(只认 JSON 可表达的类型,拒绝 !!js/!!python 等自定义 tag),\n // 比 js-yaml v4 默认 DEFAULT_SCHEMA 更严;也避免无引号日期被解析成 Date 对象。\n let parsed: unknown;\n try {\n parsed = yaml.load(yamlText, { schema: JSON_SCHEMA });\n } catch (e) {\n return fail(`YAML 解析失败: ${(e as Error).message}`);\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return fail('fixture 为空或不是 mapping');\n }\n const doc = parsed as Record<string, unknown>;\n for (const f of REQUIRED) {\n if (doc[f] === undefined) return fail(`fixture 缺必需字段 \"${f}\"`);\n }\n if (!Array.isArray(doc.items)) return fail('\"items\" 必须是 list');\n if (!Number.isInteger(doc.count)) return fail('\"count\" 必须是整数');\n if (doc.items.length !== doc.count) {\n return fail(`count mismatch: count=${doc.count} 但 items 有 ${doc.items.length}`);\n }\n\n const keyField = doc.key_field as string;\n const seen = new Set<string>();\n for (const item of doc.items as Array<Record<string, unknown>>) {\n const k = item[keyField];\n if (k === undefined || k === null || k === '') return fail(`item 缺 key_field \"${keyField}\"`);\n if (seen.has(String(k))) return fail(`duplicate key \"${k}\" in ${String(doc.model)}`);\n seen.add(String(k));\n }\n return { ...(doc as unknown as Fixture), sourcePath };\n}\n","import { camelizeKeys } from './fields';\nimport { RefStore, resolveRefs } from './placeholder';\nimport type { Fixture, ImportOptions, ImportResult, RowResult } from './types';\n\n/** runner 只依赖 client 的这三个方法,便于单测 stub。 */\nexport interface ImportClient {\n findIdByKey(model: string, keyField: string, keyValue: string): Promise<string | null>;\n create(model: string, data: Record<string, unknown>): Promise<{ id: string }>;\n update(model: string, id: string, data: Record<string, unknown>): Promise<unknown>;\n}\n\nexport async function runImport(\n client: ImportClient,\n fixtures: Fixture[],\n opts: ImportOptions = {},\n): Promise<ImportResult> {\n const store = new RefStore();\n const rows: RowResult[] = [];\n const tally = { created: 0, updated: 0, skipped: 0, failed: 0 };\n const result = (): ImportResult => ({ ...tally, rows, refs: store.snapshot() });\n\n for (const fx of fixtures) {\n for (const item of fx.items) {\n const key = String(item[fx.key_field]);\n const { resolved, unresolved } = resolveRefs(item, store);\n\n if (unresolved.length > 0) {\n rows.push({ model: fx.model, key, status: 'failed', error: `unresolved placeholder(s): ${unresolved.join(', ')}` });\n tally.failed++;\n if (opts.onError === 'stop') return result();\n continue;\n }\n\n if (opts.dryRun) {\n // 纯静态:不写后端;注册假 id 让后续跨文件占位符仍能解析\n store.register(fx.model, key, `dryrun-${fx.model}-${key}`);\n rows.push({ model: fx.model, key, status: 'skipped' });\n tally.skipped++;\n continue;\n }\n\n const payload = camelizeKeys(resolved);\n try {\n const existingId = await client.findIdByKey(fx.model, fx.key_field, key);\n if (existingId && !opts.updateOnly) {\n store.register(fx.model, key, existingId);\n rows.push({ model: fx.model, key, status: 'skipped', id: existingId });\n tally.skipped++;\n } else if (existingId && opts.updateOnly) {\n await client.update(fx.model, existingId, payload);\n store.register(fx.model, key, existingId);\n rows.push({ model: fx.model, key, status: 'updated', id: existingId });\n tally.updated++;\n } else {\n const created = await client.create(fx.model, payload);\n store.register(fx.model, key, created.id);\n rows.push({ model: fx.model, key, status: 'created', id: created.id });\n tally.created++;\n }\n } catch (e: unknown) {\n const err = e as { response?: { headers?: Record<string, string>; data?: { message?: string } }; message?: string };\n rows.push({\n model: fx.model, key, status: 'failed',\n error: err.response?.data?.message ?? err.message ?? 'write failed',\n traceId: err.response?.headers?.['x-trace-id'],\n });\n tally.failed++;\n if (opts.onError === 'stop') return result();\n }\n }\n }\n return result();\n}\n","import type { AxiosInstance } from 'axios';\nimport { createHttpClient } from './http';\nimport { TokenStore } from './auth/token-store';\nimport { refreshToken, needsRefresh } from './auth/refresh';\nimport { loadProfile, loadGlobalConfig, type ProfileConfig } from './config';\nimport { query as modelQuery } from './models/query';\nimport { action as modelAction } from './models/action';\nimport { describe as describeModel } from './meta/describe';\nimport type { ModelQueryDsl, QueryResult, ActionInput, ActionResult } from './models/types';\nimport type { ModelDescription, DescribeOpts } from './meta/describe';\nimport {\n create as modelCreate,\n update as modelUpdate,\n remove as modelRemove,\n type CreateResult,\n type RemoveResult,\n} from './models';\nimport { runImport, type ImportClient } from './import';\nimport type { Fixture, ImportOptions, ImportResult } from './import';\n\nexport interface HcmClientOpts {\n endpoint: string;\n tenantId: string;\n accessToken?: string; // 显式构造时传\n scheme?: 'client_credentials' | 'pat';\n profile?: string;\n timeoutMs?: number;\n}\n\n/**\n * Structural type representing the resolved auth context that\n * `HcmClient.fromAuthContext` consumes. Kept as a structural interface\n * (rather than importing `AuthContext` from `@hcmai/cli`) to avoid a circular\n * package dependency — the CLI's `AuthContext` is assignable to this shape.\n */\nexport interface ClientAuthContext {\n env: string;\n endpoint: string;\n tenantId: string;\n identity: string;\n}\n\nexport class HcmClient {\n readonly profile: string;\n readonly endpoint: string;\n readonly tenantId: string;\n private http: AxiosInstance;\n private tokenStore: TokenStore;\n private explicitToken?: string;\n\n constructor(opts: HcmClientOpts) {\n this.profile = opts.profile ?? 'default';\n this.endpoint = opts.endpoint;\n this.tenantId = opts.tenantId;\n this.tokenStore = new TokenStore(this.profile);\n this.explicitToken = opts.accessToken;\n this.http = createHttpClient({\n endpoint: opts.endpoint,\n timeoutMs: opts.timeoutMs,\n getAccessToken: () => this.getAccessToken(),\n onRefresh: () => this.handleRefresh(),\n });\n }\n\n /**\n * Build a client from a resolved env + identity context (T15).\n *\n * Uses `TokenStore.forIdentity(ctx.identity)` so the token is read from\n * the identity-scoped store (written by `hcm login --env <env> --name <id>`),\n * not the legacy profile-scoped credentials.json.\n *\n * The `profile` field is kept = `ctx.env` for downstream callers that still\n * read `client.profile` (e.g. workspace context dump). Phase 2 will rename\n * the field to `env`.\n *\n * Refresh path: fromAuthContext binds `this.identity` so handleRefresh\n * routes refresh through TokenStore.forIdentity — works for\n * client_credentials sessions (1h TTL) post-T8 migration.\n */\n /**\n * Identity name for refresh path (set by fromAuthContext). When set,\n * handleRefresh routes through TokenStore.forIdentity rather than the\n * profile-scoped legacy store — critical for client_credentials sessions\n * (~1h token TTL) on post-T8-migration installs.\n */\n private identity?: string;\n\n static fromAuthContext(ctx: ClientAuthContext): HcmClient {\n const c = new HcmClient({\n endpoint: ctx.endpoint,\n tenantId: ctx.tenantId,\n profile: ctx.env, // alias env→profile for downstream readers\n });\n (c as unknown as { tokenStore: TokenStore }).tokenStore = TokenStore.forIdentity(ctx.identity);\n (c as unknown as { identity: string }).identity = ctx.identity;\n return c;\n }\n\n /**\n * @deprecated Use `HcmClient.fromAuthContext(ctx)` together with the CLI\n * helper `getActiveAuthContextFromCli(args)`. WILL THROW on post-T8\n * installs where `~/.config/hcm/<profile>/profile.yml` was migrated to\n * `envs/<name>/env.yml` (loadProfile reads the legacy path only).\n * Kept only for dead-code paths (mini.ts) and pre-migration users.\n */\n static async fromConfig(args: { profile?: string }): Promise<HcmClient> {\n const global = await loadGlobalConfig();\n const profile = args.profile ?? process.env.HCM_PROFILE ?? global.activeEnv;\n const cfg: ProfileConfig = await loadProfile(profile);\n return new HcmClient({\n endpoint: cfg.endpoint,\n tenantId: cfg.tenantId,\n profile,\n });\n }\n\n query<T = Record<string, unknown>>(model: string, dsl: ModelQueryDsl): Promise<QueryResult<T>> {\n return modelQuery<T>(this.http, model, dsl);\n }\n\n action<R = unknown>(model: string, name: string, input: ActionInput): Promise<ActionResult<R>> {\n return modelAction<R>(this.http, model, name, input);\n }\n\n describe(model: string, opts?: DescribeOpts): Promise<ModelDescription> {\n return describeModel(this.http, model, opts);\n }\n\n create(model: string, data: Record<string, unknown>): Promise<CreateResult> {\n return modelCreate(this.http, model, data);\n }\n\n update(model: string, id: string, data: Record<string, unknown>): Promise<CreateResult> {\n return modelUpdate(this.http, model, id, data);\n }\n\n remove(model: string, id: string): Promise<RemoveResult> {\n return modelRemove(this.http, model, id);\n }\n\n /** 按业务键查已存在记录的 id(幂等用);命中返回 id,否则 null。 */\n async findIdByKey(model: string, keyField: string, keyValue: string): Promise<string | null> {\n const res = await this.query(model, { filter: { [keyField]: keyValue }, limit: 1 });\n const row = res.rows[0] as { id?: string } | undefined;\n return row?.id ?? null;\n }\n\n importPackage(fixtures: Fixture[], opts?: ImportOptions): Promise<ImportResult> {\n return runImport(this as ImportClient, fixtures, opts);\n }\n\n raw(): AxiosInstance {\n return this.http;\n }\n\n async close(): Promise<void> {\n /* nothing for REST; chat module will override */\n }\n\n async getAccessToken(): Promise<string | null> {\n if (this.explicitToken) return this.explicitToken;\n return this.tokenStore.getAccessToken();\n }\n\n async handleRefresh(): Promise<void> {\n if (this.explicitToken) {\n // 显式 token 模式不能 refresh\n throw new Error('explicit token expired; pass a new accessToken');\n }\n const meta = await this.tokenStore.readMeta();\n if (!meta) throw new Error('no credentials');\n if (meta.scheme === 'pat') throw new Error('PAT cannot refresh; re-run login');\n if (needsRefresh(meta, 5)) {\n // Route refresh through identity-scoped store when bound via\n // fromAuthContext (T15 follow-up #3 — fixes client_credentials sessions\n // breaking ~1h in post-T8 installs).\n const refreshArgs = this.identity\n ? { endpoint: this.endpoint, identity: this.identity, httpClient: this.http }\n : { endpoint: this.endpoint, profile: this.profile, httpClient: this.http };\n await refreshToken(refreshArgs);\n }\n }\n}\n","import * as fs from 'node:fs';\nimport * as fsp from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { AxiosInstance } from 'axios';\n\n/**\n * 会话附件元数据——与后端 `ConversationWebSocketHandler.parseAttachments` 形态一致\n * (message.create payload.attachments 元素;#1849 后随 USER_MESSAGE 入 journal 单源)。\n */\nexport interface AttachmentMeta {\n documentId: string;\n fileName: string;\n mimeType: string;\n fileSize: number;\n}\n\nconst MIME_BY_EXT: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n '.doc': 'application/msword',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n '.csv': 'text/csv',\n '.txt': 'text/plain',\n '.md': 'text/markdown',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n};\n\nexport function guessMimeType(filePath: string): string {\n return MIME_BY_EXT[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n}\n\n/**\n * 聊天附件的存储分类 —— `hcm chat` 走这一类,与 web ChatInput 同端点同分类。\n *\n * <p>⚠️ 它**不是**通用默认值:`storageCategory` 是服务端的一份**数据**(可配置的\n * 分类表),不是枚举常量。分类不存在时服务端返回 404 `category not found`。\n */\nexport const CHAT_ATTACHMENT_CATEGORY = 'ai-conversation';\n\n/**\n * 上传本地文件到 HCM 文档存储,返回可直接携带的附件元数据。\n * 类型限制跟随服务端校验。\n *\n * @param storageCategory 存储分类。**必须是目标环境里真实存在的分类**——\n * 它是服务端数据而非枚举,不同环境的分类表可以不同。\n * 聊天附件传 {@link CHAT_ATTACHMENT_CATEGORY};报表模板一类传 `flex-report`。\n *\n * <p>🔴 此前这里硬编码 `'ai-conversation'`,于是 `hcm upload <任意文件>` 在\n * 没有该分类的环境上**一律失败**,而错误被包成\n * `BUSINESS_RULE_VIOLATION: Request failed with status code 404`——看不出真因是\n * 「分类不存在」。2026-08-26 按交付技能跑弹性报表端到端时,Step 4「上传模板」\n * 卡在这里;模板是报表的必需件,这条不通整条链就断了。\n */\nexport async function uploadDocument(\n http: AxiosInstance,\n filePath: string,\n storageCategory: string = CHAT_ATTACHMENT_CATEGORY,\n): Promise<AttachmentMeta> {\n const abs = expandHome(filePath);\n const buf = await fsp.readFile(abs);\n const fileName = path.basename(abs);\n const form = new FormData();\n form.append('file', new Blob([buf], { type: guessMimeType(abs) }), fileName);\n form.append('storageCategory', storageCategory);\n form.append('allowEmptyFormId', 'true');\n const resp = await http.post('/api/documents/upload', form);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const meta: any = resp.data?.data ?? resp.data;\n return {\n documentId: String(meta.documentId ?? meta.id),\n fileName: String(meta.fileName ?? fileName),\n mimeType: String(meta.contentType ?? meta.mimeType ?? guessMimeType(abs)),\n fileSize: Number(meta.fileSize ?? meta.size ?? buf.length),\n };\n}\n\n/** 默认产物落盘目录(对标 Claude Code 工具产物本地直接访问)。 */\nexport function defaultDownloadDir(): string {\n return path.join(os.homedir(), 'Downloads', 'hcm');\n}\n\n/**\n * 把后端相对下载链接(`/api/documents/download/{id}?...sig=`)拼成可点的绝对 URL,\n * 供终端渲染——签名 URL 自洽免 Bearer,点开即由浏览器打开(ADR-140)。\n * apiBase 为 CLI 连接的后端地址(`HcmClient.endpoint`);无 apiBase 或已是绝对 URL 时原样返回。\n */\nexport function absoluteDownloadUrl(apiBase: string | undefined, url: string): string {\n if (!apiBase || !url.startsWith('/')) return url;\n return apiBase.replace(/\\/+$/, '') + url;\n}\n\n/**\n * 认证下载文档到本地(`/api/documents/download` 需 Bearer,浏览器裸 GET 恒 401 的同款教训)。\n * destPath 为目录或省略时落 defaultDownloadDir,重名自动加序号。返回写入的绝对路径。\n */\nexport async function downloadDocument(\n http: AxiosInstance,\n ref: { documentId?: string; downloadUrl?: string; fileName?: string },\n destPath?: string,\n): Promise<string> {\n const url = ref.downloadUrl ?? `/api/documents/download/${ref.documentId}`;\n const resp = await http.get(url, { responseType: 'arraybuffer' });\n const headerName = parseFilenameFromDisposition(resp.headers?.['content-disposition']);\n // 🔴 路径穿越收口:fileName 来自服务端(content-disposition)/ 工具 result(LLM 可控),\n // safeBasename 剥掉所有目录段——杜绝 \"../../../.ssh/authorized_keys\" 类静默任意文件写\n // (auto-download 默认开,恶意工具 result 否则可写任意路径)。\n const rawName = ref.fileName ?? headerName ?? `${ref.documentId ?? 'download'}.bin`;\n\n // 目标目录与文件名解析:destPath 为目录/省略 → 落该目录 + 服务端 fileName;\n // destPath 指向文件 → 落其父目录 + destPath 的 basename(用户显式命名)。\n let dir: string;\n let nameSource: string;\n const expanded = destPath ? expandHome(destPath) : undefined;\n if (expanded && (expanded.endsWith(path.sep) || isExistingDir(expanded))) {\n dir = expanded;\n nameSource = rawName;\n } else if (expanded) {\n dir = path.dirname(expanded);\n nameSource = path.basename(expanded);\n } else {\n dir = defaultDownloadDir();\n nameSource = rawName;\n }\n const finalName = safeBasename(nameSource, ref.documentId);\n let target = path.join(dir, finalName);\n // 纵深防御:归属断言——target 必须在 dir 内(safeBasename 后理应恒真,留作回潮保险)。\n const resolvedDir = path.resolve(dir);\n if (path.resolve(target) !== path.join(resolvedDir, finalName)) {\n throw new Error(`下载路径越界:${target} 不在 ${resolvedDir} 内`);\n }\n await fsp.mkdir(dir, { recursive: true });\n target = dedupePath(target);\n await fsp.writeFile(target, Buffer.from(resp.data as ArrayBuffer));\n return target;\n}\n\nfunction expandHome(p: string): string {\n return p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : path.resolve(p);\n}\n\n/**\n * 文件名净化:剥所有目录段(path.basename 处理 / 与 \\\\)+ 拒 .. / 空 / 纯点。\n * 退化时回落 documentId 或 'download',绝不返回可逃出目标目录的串。\n */\nfunction safeBasename(rawName: string, documentId?: string): string {\n // 先归一反斜杠(防 Windows 风格 ..\\\\ 在 posix path.basename 下漏网)。\n const base = path.basename(rawName.replace(/\\\\/g, '/'));\n if (!base || base === '.' || base === '..' || base.includes('/')) {\n return documentId ? `${documentId}.bin` : 'download.bin';\n }\n return base;\n}\n\nfunction isExistingDir(p: string): boolean {\n try {\n return fs.statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** 重名文件加序号:report.xlsx → report (1).xlsx。 */\nfunction dedupePath(target: string): string {\n if (!fs.existsSync(target)) return target;\n const dir = path.dirname(target);\n const ext = path.extname(target);\n const base = path.basename(target, ext);\n for (let i = 1; ; i++) {\n const candidate = path.join(dir, `${base} (${i})${ext}`);\n if (!fs.existsSync(candidate)) return candidate;\n }\n}\n\nfunction parseFilenameFromDisposition(disposition?: string): string | undefined {\n if (!disposition) return undefined;\n const star = /filename\\*=UTF-8''([^;]+)/i.exec(disposition);\n if (star?.[1]) {\n try {\n return decodeURIComponent(star[1]);\n } catch {\n /* fallthrough */\n }\n }\n const plain = /filename=\"?([^\";]+)\"?/i.exec(disposition);\n return plain?.[1];\n}\n","export function toJson(value: unknown): string {\n return JSON.stringify(value, null, 2);\n}\n","import yaml from 'js-yaml';\n\nexport function toYaml(value: unknown): string {\n return yaml.dump(value, { indent: 2, lineWidth: -1 });\n}\n","import Table from 'cli-table3';\nimport { displayWidth, truncateDisplay } from '../chat/display-width';\n\nconst MAX_COL_WIDTH = 40;\n\n/**\n * 盒式文本表格。列宽**按内容自适应**、以 {@link MAX_COL_WIDTH} 为上限——短列(SEQ/ROUND\n * 等)不再被强制撑到 40 列(旧 `colWidths.map(()=>40)` 的 BUG:7 列表 ≈ 290 列宽、糊成一片)。\n * 宽度与截断均按**终端显示宽度**(CJK=2 列)计,避免中文单元格溢出错位。\n */\nexport function toTable(rows: Record<string, unknown>[]): string {\n if (rows.length === 0) return '(no results)';\n const cols = Object.keys(rows[0]!);\n const heads = cols.map((c) => c.toUpperCase());\n const cells = rows.map((r) => cols.map((c) => formatCell(r[c])));\n\n // 每列内容宽 = min(上限, max(表头, 各单元格显示宽))。\n const contentWidths = cols.map((_, i) =>\n Math.min(\n MAX_COL_WIDTH,\n Math.max(displayWidth(heads[i]!), ...cells.map((row) => displayWidth(row[i]!))),\n ),\n );\n\n const table = new Table({\n head: heads,\n style: { head: ['cyan'] },\n // +2:cli-table3 的 colWidths 含左右各 1 列 padding。\n colWidths: contentWidths.map((w) => w + 2),\n wordWrap: true,\n });\n for (const row of cells) table.push(row);\n return table.toString();\n}\n\nfunction formatCell(v: unknown): string {\n if (v === null || v === undefined) return '';\n const s = typeof v === 'object' ? JSON.stringify(v) : String(v);\n return truncateDisplay(s, MAX_COL_WIDTH);\n}\n","/**\n * 终端显示宽度(east-asian-width 近似,零依赖):CJK 表意/全角标点/假名/谚文按 2 列,\n * 其余按 1 列。argsPreview 等终端单行截断必须按显示宽度——按字符数截 120 的中文实际\n * 占 240 列必折行(2026-06-11 实测工具行折行根因)。\n */\nfunction charDisplayWidth(cp: number): number {\n return (cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo\n (cp >= 0x2e80 && cp <= 0xa4cf) || // CJK 部首/注音/汉字/假名\n (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul 音节\n (cp >= 0xf900 && cp <= 0xfaff) || // CJK 兼容表意\n (cp >= 0xfe30 && cp <= 0xfe4f) || // CJK 兼容形式\n (cp >= 0xff00 && cp <= 0xff60) || // 全角 ASCII/标点\n (cp >= 0xffe0 && cp <= 0xffe6) ||\n (cp >= 0x20000 && cp <= 0x3fffd) // CJK 扩展 B+\n ? 2\n : 1;\n}\n\n/** 字符串的终端显示宽度(列数)。 */\nexport function displayWidth(s: string): number {\n let w = 0;\n for (const ch of s) w += charDisplayWidth(ch.codePointAt(0) as number);\n return w;\n}\n\n/** 按显示宽度截断,超宽时截到 maxWidth-1 列并补「…」;未超宽原样返回。 */\nexport function truncateDisplay(s: string, maxWidth: number): string {\n if (displayWidth(s) <= maxWidth) return s;\n let w = 0;\n let out = '';\n for (const ch of s) {\n const cw = charDisplayWidth(ch.codePointAt(0) as number);\n if (w + cw > maxWidth - 1) break;\n w += cw;\n out += ch;\n }\n return out + '…';\n}\n","import { toJson } from './json';\nimport { toYaml } from './yaml';\nimport { toTable } from './table';\n\nexport type OutputFormat = 'table' | 'json' | 'yaml';\n\nexport interface FormatOptions {\n format: OutputFormat;\n}\n\nexport function formatRows(rows: Record<string, unknown>[], opts: FormatOptions): string {\n switch (opts.format) {\n case 'json':\n return toJson(rows);\n case 'yaml':\n return toYaml(rows);\n case 'table':\n return toTable(rows);\n }\n}\n\nexport function formatObject(obj: unknown, opts: FormatOptions): string {\n switch (opts.format) {\n case 'json':\n return toJson(obj);\n case 'yaml':\n return toYaml(obj);\n case 'table':\n // 单对象 fallback 到 json\n return toJson(obj);\n }\n}\n\nexport { toJson, toYaml, toTable };\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, type TokenRecord } from './token-store';\nimport { type LoginOutcome } from './principal';\n\nexport interface ClientCredentialsInput {\n endpoint: string;\n tenantId: string;\n clientId: string;\n clientSecret: string;\n profile: string;\n httpClient: AxiosInstance;\n}\n\nexport async function loginClientCredentials(\n input: ClientCredentialsInput,\n): Promise<LoginOutcome> {\n const resp = await input.httpClient.post(\n `${input.endpoint}/api/oauth/token`,\n {\n grant_type: 'client_credentials',\n client_id: input.clientId,\n client_secret: input.clientSecret,\n },\n { headers: { 'Content-Type': 'application/json' } },\n );\n const { access_token, refresh_token, expires_in, scope } = resp.data;\n if (!access_token) {\n throw new Error('OAuth response missing access_token');\n }\n\n const ttlSeconds = expires_in ?? 3600;\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();\n const scopes = (scope ?? '').split(/\\s+/).filter(Boolean);\n\n const store = new TokenStore(input.profile);\n const accessTokenRef = await store.setAccessToken(access_token);\n const refreshTokenRef = refresh_token ? await store.setRefreshToken(refresh_token) : undefined;\n\n const record: TokenRecord = {\n scheme: 'client_credentials',\n accessTokenRef,\n refreshTokenRef,\n expiresAt,\n scopes,\n ttlSeconds,\n };\n await store.writeMeta(record);\n // client_credentials 是机器身份,服务端不回 user —— principal 恒为 undefined,\n // 这与「口令登录拿不到身份」是两件事,别在回显上合并。\n return { record };\n}\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, type TokenRecord } from './token-store';\nimport { toPrincipal, type LoginOutcome } from './principal';\n\nexport interface PatLoginInput {\n endpoint: string;\n tenantId: string;\n pat: string;\n profile: string;\n httpClient: AxiosInstance;\n}\n\nexport async function loginPat(input: PatLoginInput): Promise<LoginOutcome> {\n // 验证 PAT 有效 + 拿用户/scope 信息\n const resp = await input.httpClient.get(`${input.endpoint}/api/auth/info`, {\n headers: { Authorization: `Bearer ${input.pat}` },\n });\n const scopes: string[] = resp.data?.scopes ?? [];\n // `/api/auth/info` 回的是 UserInfoResponse(userId/username/tenantId/roles) ——\n // PAT 到底挂在谁头上,只有这里说得出来。\n\n const store = new TokenStore(input.profile);\n const accessTokenRef = await store.setAccessToken(input.pat);\n\n // PAT 没有 refresh,过期时间设为 1 年(PAT 由后端管失效)\n const expiresAt = new Date(Date.now() + 365 * 24 * 3600 * 1000).toISOString();\n\n const record: TokenRecord = {\n scheme: 'pat',\n accessTokenRef,\n expiresAt,\n scopes,\n };\n await store.writeMeta(record);\n return { record, principal: toPrincipal(resp.data) };\n}\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, type TokenRecord } from './token-store';\nimport { toPrincipal, type LoginOutcome } from './principal';\nimport { DEFAULT_SESSION_TTL_SECONDS } from './password';\n\nexport interface PairingLoginInput {\n endpoint: string;\n /** 页面上复制来的配对码。大小写与连字符由服务端归一化,这里原样传。 */\n code: string;\n profile: string;\n httpClient: AxiosInstance;\n}\n\n/**\n * 配对码登录(ADR-308 第四轨)。\n *\n * 解的是「没有任何一方知道后端地址与 tenantId」:CLI 只有磁盘上人手填的值,用户知道的\n * 是浏览器地址栏那一串,后端全树无 frontend URL 配置。唯一同时知道这两件事的是那个\n * 已登录的页面——所以地址由命令行位置参数带进来(人从页面复制整行),租户由服务端\n * 在响应里给出,**不再有 `'1'` 这个猜测**。\n *\n * 🔴 `scheme` 记 `'pairing'` 而不是 `'password'`:如实记录来路(审计与回显需要)。\n * 但它换来的是 `TokenManager.createToken` 铸的**普通会话 token**,续期/吊销语义与口令\n * 登录完全一致——这一点由 `isServerSlidingSession` 谓词保证,别在别处写\n * `=== 'password'` 的比较。\n */\nexport async function loginPairing(input: PairingLoginInput): Promise<LoginOutcome> {\n const resp = await input.httpClient.post(\n `${input.endpoint}/api/auth/cli/exchange`,\n { code: input.code },\n { headers: { 'Content-Type': 'application/json' } },\n );\n // 响应刻意与 /api/auth/login 同形(LoginResponse),所以这里能与口令轨共用解析。\n const { token, expiresIn, user } = resp.data;\n if (!token) {\n throw new Error('Pairing exchange response missing token');\n }\n\n const ttlSeconds = expiresIn ?? DEFAULT_SESSION_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();\n\n const store = new TokenStore(input.profile);\n const accessTokenRef = await store.setAccessToken(token);\n\n const record: TokenRecord = {\n scheme: 'pairing',\n accessTokenRef,\n // 与口令会话同样没有 refresh token:续期语义是「同一个 token 被服务端重设 TTL」。\n refreshTokenRef: undefined,\n expiresAt,\n scopes: ['*'],\n ttlSeconds,\n };\n await store.writeMeta(record);\n return { record, principal: toPrincipal(user) };\n}\n\n/**\n * 从用户粘贴的地址里取出 origin。\n *\n * 人复制的是**浏览器地址栏那一串**,带路径、带查询参数都正常——那正是他唯一确定知道的\n * 东西。要求他先手工削成 origin,等于把「知道后端地址」这个门槛又搬了回来。\n */\nexport function toOrigin(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `https://${raw}`;\n const url = new URL(withScheme);\n return url.origin;\n}\n\n/**\n * 由 origin 推一个 env 名:`https://hcm.custa.com` → `custa`。\n *\n * 取的是主域左边那一段而不是整个 hostname:`hcm.custa.com` 与 `hcm-test.custa.com`\n * 会推出不同的名字,而 `hcm.custa.com` 与 `www.custa.com` 也不会撞。\n * 推不出来时落到 `default`,由调用方处理重名。\n */\nexport function inferEnvName(origin: string): string {\n const host = new URL(origin).hostname;\n if (/^\\d+(\\.\\d+)*$/.test(host) || host === 'localhost') return 'local';\n const parts = host.split('.').filter(Boolean);\n if (parts.length === 0) return 'default';\n // hcm.custa.com → custa(倒数第二段);custa.com → custa;单段 → 该段\n const candidate = parts.length >= 2 ? parts[parts.length - 2] : parts[0];\n return candidate || 'default';\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 首登强制改密(fresh tenant 的必经一步)。\n *\n * <p>后端契约(AuthController):\n * <ul>\n * <li>用初始口令 `POST /api/auth/login` → **403**,body 是\n * `{error:{code:\"AUTH_PASSWORD_CHANGE_REQUIRED\", details:{changePasswordToken, expiresInSeconds}}}`\n * —— 那个临时 token **只活 300 秒**;</li>\n * <li>拿它作 Bearer 调 `POST /api/auth/change-password`,body `{oldPassword,newPassword,confirmPassword}`;</li>\n * <li>改完用新口令重新 login 才拿到正式 token。</li>\n * </ul>\n *\n * <p>🔴 这是 `hcm tenant bootstrap` 之后的**硬阻塞**:不走这一步,新建的租户登不进去。\n * 所以 `hcm login` 自动识别并接管,而不是让顾问自己去拼两条 curl。\n */\n\nexport const PASSWORD_CHANGE_REQUIRED = 'AUTH_PASSWORD_CHANGE_REQUIRED';\n\nexport interface PasswordChangeChallenge {\n changePasswordToken: string;\n expiresInSeconds?: number;\n}\n\n/**\n * 从一个失败的 login 响应里识别「强制改密」挑战。\n *\n * 不是该挑战(网络错、口令错、账号锁定…)一律返回 null,由调用方按原错误处理——\n * 判据只认后端的错误码,不靠 HTTP 状态猜(423 账号锁定同样是 4xx,不能混)。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function detectPasswordChangeChallenge(err: any): PasswordChangeChallenge | null {\n const body = err?.response?.data;\n const error = body?.error ?? body;\n if (error?.code !== PASSWORD_CHANGE_REQUIRED) return null;\n const token = error?.details?.changePasswordToken;\n if (!token) return null;\n return {\n changePasswordToken: String(token),\n expiresInSeconds: Number(error?.details?.expiresInSeconds) || undefined,\n };\n}\n\nexport interface ChangePasswordInput {\n endpoint: string;\n /** 登录挑战里拿到的临时 token(300 秒有效) */\n changePasswordToken: string;\n oldPassword: string;\n newPassword: string;\n httpClient: AxiosInstance;\n}\n\nexport async function changePassword(input: ChangePasswordInput): Promise<void> {\n await input.httpClient.post(\n `${input.endpoint}/api/auth/change-password`,\n {\n oldPassword: input.oldPassword,\n newPassword: input.newPassword,\n confirmPassword: input.newPassword,\n },\n {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${input.changePasswordToken}`,\n },\n },\n );\n}\n","import type { AxiosInstance } from 'axios';\n\nexport interface AdminResetPasswordRequest {\n userId?: string;\n username?: string;\n newPassword: string;\n confirmPassword: string;\n forceChangeOnNextLogin: boolean;\n}\n\nexport interface AdminResetPasswordResult {\n status: 'PASSWORD_RESET';\n userId: string;\n forceChangeOnNextLogin: boolean;\n credentialVersion: number;\n sessionsRevoked: boolean;\n removedLoginAttemptKeys: number;\n}\n\n/**\n * 管理员重置租户内用户的本地登录口令。\n *\n * 该端点属于专用凭据流:密码只存在于 HTTPS 请求体,不经过 Model 元数据、\n * Action 参数、日志或返回值。调用方仍须避免把密码放进 argv。\n */\nexport async function adminResetPassword(\n http: AxiosInstance,\n input: AdminResetPasswordRequest,\n): Promise<AdminResetPasswordResult> {\n const response = await http.post('/api/admin/users/password-reset', input);\n const payload = response.data?.data ?? response.data;\n return payload as AdminResetPasswordResult;\n}\n","import WebSocket from 'ws';\nimport { CliError, CliErrorCode } from '../errors';\nimport type {\n StreamCommandEnvelope,\n StreamEventEnvelope,\n ConnectionMessage,\n ControlChannelEnvelope,\n} from './protocol';\n\nexport type MessageClass = 'control' | 'event' | 'connection';\n\n/** WS 入站消息三分:控制通道(channel='control')优先于流事件(streamId+seq),其余为连接层。 */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function classifyMessage(parsed: any): MessageClass {\n if (parsed?.channel === 'control') return 'control';\n if (parsed?.streamId && typeof parsed.seq === 'number') return 'event';\n return 'connection';\n}\n\nexport interface WsClientOpts {\n endpoint: string; // wss://host/ws/conversation\n getAccessToken: () => Promise<string | null>;\n onTokenExpiring?: () => Promise<void>; // 后台 refresh\n heartbeatMs?: number;\n}\n\ntype Handler =\n | { kind: 'event'; fn: (ev: StreamEventEnvelope) => void }\n | { kind: 'connection'; fn: (msg: ConnectionMessage) => void }\n | { kind: 'control'; fn: (ev: ControlChannelEnvelope) => void };\n\nexport class WsClient {\n private ws?: WebSocket;\n private handlers: Handler[] = [];\n private heartbeatTimer?: NodeJS.Timeout;\n private ready = false;\n private sessionId?: string;\n /** 流进行中断连的持久订阅者(iterator 等终态收口者)。RISK-1:auth 后无持久\n * close handler,断连时上层 await 永久挂死。这里让消费方能感知 socket 死。 */\n private disconnectHandlers = new Set<() => void>();\n /** 由 close()/ensureConnected 主动关闭 → 不广播 disconnect(非异常断连)。 */\n private intentionalClose = false;\n\n constructor(private opts: WsClientOpts) {}\n\n /**\n * 订阅「流进行中 socket 异常断连」(后端重启 / 网络闪断)。主动 close() 不触发。\n * iterator 订阅它 → 断连时 finish()(incomplete 终态),杜绝 await consume 永久挂死。\n */\n onDisconnect(fn: () => void): () => void {\n this.disconnectHandlers.add(fn);\n return () => {\n this.disconnectHandlers.delete(fn);\n };\n }\n\n private notifyDisconnect(): void {\n if (this.intentionalClose) return;\n this.ready = false;\n for (const fn of [...this.disconnectHandlers]) {\n try {\n fn();\n } catch {\n /* 单个订阅者抛错不影响其他 */\n }\n }\n }\n\n async connect(): Promise<void> {\n const tok = await this.opts.getAccessToken();\n if (!tok) {\n throw new CliError({\n code: CliErrorCode.AUTH_FAILED,\n message: 'no access token; run `hcm login`',\n });\n }\n this.ws = new WebSocket(this.opts.endpoint);\n\n return new Promise<void>((resolve, reject) => {\n const settle = (fn: () => void) => {\n off();\n fn();\n };\n const off = () => {\n this.ws?.removeListener('open', onOpen);\n this.ws?.removeListener('message', onMessage);\n this.ws?.removeListener('error', onError);\n this.ws?.removeListener('close', onClose);\n };\n const onOpen = () => {\n this.ws?.send(JSON.stringify({ type: 'auth', token: tok }));\n };\n const onMessage = (data: WebSocket.RawData) => {\n const text = data.toString();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let parsed: any;\n try {\n parsed = JSON.parse(text);\n } catch {\n return;\n }\n\n // 三分:控制通道 vs 流事件 vs 连接层\n switch (classifyMessage(parsed)) {\n case 'control':\n this.dispatchControl(parsed as ControlChannelEnvelope);\n return;\n case 'event':\n this.dispatchEvent(parsed as StreamEventEnvelope);\n return;\n case 'connection':\n break;\n }\n const msg = parsed as ConnectionMessage;\n this.dispatchConnection(msg);\n\n if (msg.type === 'auth_success') {\n this.ready = true;\n this.intentionalClose = false;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.sessionId = (msg as any).sessionId;\n this.startHeartbeat();\n settle(resolve);\n // 装上持久 listener,接管后续流事件 / 连接层消息\n this.ws!.on('message', this.persistentOnMessage);\n // RISK-1:持久 close/error listener——auth 后 settle 的 off() 摘掉了建连期\n // 的 onClose,此后无人感知 socket 死。流进行中异常断连时广播 disconnect,\n // 让 iterator finish() 收口,杜绝 await consume 永久挂死。\n this.ws!.on('close', this.persistentOnClose);\n this.ws!.on('error', this.persistentOnClose);\n } else if (msg.type === 'auth_error') {\n settle(() =>\n reject(\n new CliError({\n code: CliErrorCode.WS_AUTH_ERROR,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n message: `${(msg as any).code}: ${(msg as any).message}`,\n }),\n ),\n );\n } else if (msg.type === 'token_expiring' && this.opts.onTokenExpiring) {\n void this.opts.onTokenExpiring();\n }\n };\n const onError = (err: Error) =>\n settle(() =>\n reject(new CliError({ code: CliErrorCode.NETWORK_ERROR, message: err.message })),\n );\n const onClose = () =>\n settle(() =>\n reject(\n new CliError({ code: CliErrorCode.NETWORK_ERROR, message: 'WS closed before auth' }),\n ),\n );\n this.ws!.on('open', onOpen);\n this.ws!.on('message', onMessage);\n this.ws!.on('error', onError);\n this.ws!.on('close', onClose);\n });\n }\n\n send<P>(cmd: StreamCommandEnvelope<P>): void {\n if (!this.ready || !this.ws) {\n throw new CliError({ code: CliErrorCode.NETWORK_ERROR, message: 'WS not ready' });\n }\n this.ws.send(JSON.stringify(cmd));\n }\n\n onEvent(fn: (ev: StreamEventEnvelope) => void): () => void {\n const h: Handler = { kind: 'event', fn };\n this.handlers.push(h);\n return () => {\n this.handlers = this.handlers.filter((x) => x !== h);\n };\n }\n\n onConnection(fn: (msg: ConnectionMessage) => void): () => void {\n const h: Handler = { kind: 'connection', fn };\n this.handlers.push(h);\n return () => {\n this.handlers = this.handlers.filter((x) => x !== h);\n };\n }\n\n onControl(fn: (ev: ControlChannelEnvelope) => void): () => void {\n const h: Handler = { kind: 'control', fn };\n this.handlers.push(h);\n return () => {\n this.handlers = this.handlers.filter((x) => x !== h);\n };\n }\n\n isReady(): boolean {\n return this.ready;\n }\n\n /**\n * 连接已断(后端重启 / 网络闪断 / 心跳静默失败后 socket 已死)时重建连接。\n * REPL 长开场景每轮发消息前调用——旧连接死掉不应让用户重启 CLI(2026-06-12 用户\n * 实测:后端重启后 REPL 下一条消息报错)。已连接且 socket OPEN 时为 no-op。\n */\n async ensureConnected(): Promise<void> {\n if (this.ready && this.ws && this.ws.readyState === WebSocket.OPEN) return;\n await this.close();\n await this.connect();\n }\n\n getSessionId(): string | undefined {\n return this.sessionId;\n }\n\n async close(): Promise<void> {\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n this.ready = false;\n this.intentionalClose = true; // 主动关 → persistentOnClose 不广播 disconnect\n if (this.ws) {\n this.ws.removeListener('message', this.persistentOnMessage);\n this.ws.removeListener('close', this.persistentOnClose);\n this.ws.removeListener('error', this.persistentOnClose);\n if (this.ws.readyState === WebSocket.OPEN) this.ws.close();\n }\n }\n\n /** 流进行中 socket 异常断连(非主动 close)→ 广播 disconnect 让消费方收口。 */\n private persistentOnClose = (): void => {\n this.notifyDisconnect();\n };\n\n private persistentOnMessage = (data: WebSocket.RawData): void => {\n const text = data.toString();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let parsed: any;\n try {\n parsed = JSON.parse(text);\n } catch {\n return;\n }\n switch (classifyMessage(parsed)) {\n case 'control':\n this.dispatchControl(parsed as ControlChannelEnvelope);\n return;\n case 'event':\n this.dispatchEvent(parsed as StreamEventEnvelope);\n return;\n case 'connection':\n break;\n }\n const msg = parsed as ConnectionMessage;\n this.dispatchConnection(msg);\n if (msg.type === 'token_expiring' && this.opts.onTokenExpiring) {\n void this.opts.onTokenExpiring();\n }\n };\n\n private startHeartbeat(): void {\n const ms = this.opts.heartbeatMs ?? 30_000;\n this.heartbeatTimer = setInterval(() => {\n try {\n this.ws?.send(JSON.stringify({ type: 'ping' }));\n } catch {\n /* swallow heartbeat send errors */\n }\n }, ms);\n }\n\n private dispatchEvent(ev: StreamEventEnvelope): void {\n for (const h of this.handlers) if (h.kind === 'event') h.fn(ev);\n }\n\n private dispatchConnection(msg: ConnectionMessage): void {\n for (const h of this.handlers) if (h.kind === 'connection') h.fn(msg);\n }\n\n private dispatchControl(ev: ControlChannelEnvelope): void {\n for (const h of this.handlers) if (h.kind === 'control') h.fn(ev);\n }\n}\n","import type { AttachmentMeta } from '../documents';\nimport { randomUUID } from 'node:crypto';\nimport type { WsClient } from './ws-client';\nimport type {\n StreamEventEnvelope,\n StreamCommandEnvelope,\n MessageCreatePayload,\n ControlChannelEnvelope,\n} from './protocol';\nimport { buildInterrupt, type StreamCommand } from './control-channel';\n\nexport interface SendMessageOpts {\n conversationId?: string; // 不传 = 新建\n agentKey?: string;\n agentId?: string; // direct-ops mode: bypass conversation→agent lookup\n pageContext?: Record<string, unknown>;\n /**\n * confirm/ask 决策回发后继续消费 resume 流到真终态(Task B3)。\n *\n * v4 协议:confirm 触发时原流以 ROOT message.stop 终态(HALT 语义);决策回发后后端\n * resume 续推事件。开启本开关后终态规则变为:\n * - ROOT message.stop 且无未决交互 → 终态(含全程无交互的普通流)\n * - ROOT message.stop 且有未决交互 → hold(HALT / 串联 confirm 的中间 stop)\n * - `control.interaction.resolved(phase=TERMINAL)` 清空未决集且曾 hold 过 ROOT stop\n * → 终态兜底(TERMINAL 是 InteractionSession 生命周期事件,必达;防 resume 段缺\n * ROOT message.stop 的后端回归挂死 CLI)\n * 仅在调用方会回发决策(--on-confirm / --on-ask / 交互式确认)时开启;默认关闭,\n * 行为与历史完全一致。\n */\n holdUntilInteractionTerminal?: boolean;\n /**\n * 重新生成模式(ADR-089):不发 message.create,改发 `response.regenerate`\n * (payload {fromSeq})。fromSeq = 被重生成 assistant 段起点的 journal seq\n * (LLM_DONE;timeline chat-text item 的 seq 即它)。后端 append SEGMENT_SUPERSEDED\n * marker 后用原 user message 重跑 loop,新流事件 requestId 回显本命令 commandId\n * ——iterator 过滤与终态规则完全复用。必须配合已存在的 conversationId。\n */\n regenerateFromSeq?: number;\n /** 附件元数据(issue #64):已上传文档随消息发送,后端 parseAttachments 同形态。 */\n attachments?: AttachmentMeta[];\n}\n\nexport interface MessageStreamHandle {\n conversationId: string;\n commandId: string;\n events: AsyncIterable<StreamEventEnvelope>;\n /**\n * 客户端中断当前流(ADR-085)。\n * 发送 `stream.cancel` command 到后端;后端会回 `response.interrupted` 作 iterator 终态。\n * 调用方仍需等 iterator 走完才能确认终止。\n */\n cancel(reason?: string): void;\n /**\n * 订阅本 conversation 的带外控制通道事件(control.interaction.request / resolved)。\n * 仅转发 `ev.conversationId === conversationId` 的事件;返回取消订阅函数。\n */\n onControl(fn: (ev: ControlChannelEnvelope) => void): () => void;\n /**\n * 回发 client→server 控制命令(approval.confirm / approval.reject / interaction.answer)。\n * 由 control-channel 的 build* 构造,透传到 ws。\n */\n sendCommand(cmd: StreamCommand): void;\n}\n\nexport async function sendMessageAndStream(\n ws: WsClient,\n prompt: string,\n opts: SendMessageOpts = {},\n): Promise<MessageStreamHandle> {\n const conversationId = opts.conversationId ?? randomUUID();\n const commandId = randomUUID();\n const queue: StreamEventEnvelope[] = [];\n const waiters: Array<(ev: IteratorResult<StreamEventEnvelope>) => void> = [];\n let done = false;\n const seenEventIds = new Set<string>();\n\n // B3 · hold 模式状态:未决交互集(值 = 是否已收到 RESUMED 受理)+ 是否 hold 过\n // ROOT stop(HALT 已发生的标记)。B4 升级为 Map:接管链语义需要区分「已 RESUMED」。\n const hold = !!opts.holdUntilInteractionTerminal;\n const pendingInteractions = new Map<string, { resumed: boolean }>();\n let sawHeldRootStop = false;\n let offControlInternal: (() => void) | undefined;\n\n const finish = (): void => {\n if (done) return;\n done = true;\n while (waiters.length) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-explicit-any\n waiters.shift()!({ value: undefined as any, done: true });\n }\n off();\n offControlInternal?.();\n offDisconnect?.();\n };\n\n // RISK-1:流进行中 socket 异常断连(后端重启 / 网络闪断)→ finish()(incomplete 终态),\n // 杜绝 await consume 永久挂死。queue 中已收事件在 finish 前被消费方吐尽,断后置 done。\n // onDisconnect 可选(旧 WsClient / 测试 stub 无此方法时跳过,不破坏向后兼容)。\n const offDisconnect = ws.onDisconnect?.(() => finish());\n\n const off = ws.onEvent((ev) => {\n if (done) return;\n if (ev.requestId !== commandId) return;\n // v4 去重必须按 eventId,不能按 seq 单调(旧 v3 逻辑)。v4 的 ephemeral 事件\n // (LlmDelta text/thinking 等)共享上一个 material 的 seq(不递增),seq<=lastSeq\n // 会把 text delta + 终态事件全部误杀 → 空白回答 + 等不到 response.completed 而 timeout。\n if (ev.eventId && seenEventIds.has(ev.eventId)) return;\n if (ev.eventId) seenEventIds.add(ev.eventId);\n if (waiters.length) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n waiters.shift()!({ value: ev, done: false });\n } else {\n queue.push(ev);\n }\n\n // v3 残余终态(completed/failed/interrupted)只在老路径出现,老路径无 resume → 一律立即终态。\n if (\n ev.type === 'response.completed' ||\n ev.type === 'response.failed' ||\n ev.type === 'response.interrupted'\n ) {\n finish();\n return;\n }\n // v4 原生终态:ROOT message.stop(parentToolUseId 空)= turn 结束,与 Web v4 同。\n // 某些路径(如 hcm_respond_directly 短路)后端不发 v3 兼容残余 response.completed,\n // 不以 message.stop 兜底则 iterator 永不结束而 timeout。子流 message.stop(parentToolUseId 非空)不算终态。\n if (\n ev.type === 'message.stop' &&\n !(ev.payload as { parentToolUseId?: string } | undefined)?.parentToolUseId\n ) {\n if (!hold || pendingInteractions.size === 0) {\n finish();\n } else {\n // HALT(或串联 confirm 的中间 stop):有未决交互 → 不终态,等 resume / TERMINAL。\n sawHeldRootStop = true;\n }\n }\n });\n\n // hold 模式:内部订阅 control 通道维护未决交互集。TERMINAL resolved 清空 pending 且\n // 曾 hold 过 ROOT stop → 终态兜底(resume 段内容此刻已推完,queue 中事件仍会被吐尽)。\n if (hold) {\n offControlInternal = ws.onControl((cev) => {\n if (done || cev.conversationId !== conversationId) return;\n const p = cev.payload as { interactionId?: string; phase?: string } | undefined;\n if (!p?.interactionId) return;\n if (cev.type === 'control.interaction.request') {\n // B4 · 接管链语义(bay4 真流实测):interaction 被嵌套 confirm「接管」后\n // (前序已 RESUMED、其 resume 轮又 HALTED 交给新 confirm),后端按设计\n // 不再为前序发 TERMINAL(HALTED 分支不发)。新 request 到达 = 已 RESUMED\n // 的前序终局由新 interaction 接管 → 从 pending 清除,否则链式 confirm 下\n // 终态条件 pending==0 永不满足而挂死。未 RESUMED 的并存 interaction\n // (用例⑤)各有自己的 TERMINAL,不在清理范围。\n for (const [id, st] of pendingInteractions) {\n if (st.resumed) pendingInteractions.delete(id);\n }\n pendingInteractions.set(p.interactionId, { resumed: false });\n } else if (cev.type === 'control.interaction.resolved') {\n if (p.phase === 'RESUMED') {\n const st = pendingInteractions.get(p.interactionId);\n if (st) st.resumed = true;\n } else if (p.phase === 'TERMINAL') {\n pendingInteractions.delete(p.interactionId);\n if (pendingInteractions.size === 0 && sawHeldRootStop) finish();\n }\n }\n });\n }\n\n const cmd: StreamCommandEnvelope<MessageCreatePayload | { fromSeq: number }> =\n opts.regenerateFromSeq != null\n ? {\n // regenerate:同 conversation 重跑被废弃段,事件 requestId 回显本 commandId。\n protocolVersion: '4.0',\n commandId,\n conversationId,\n type: 'response.regenerate',\n timestamp: Date.now(),\n payload: { fromSeq: opts.regenerateFromSeq },\n }\n : {\n // v4 reference client:后端 ADR-118 起无条件推 v4,此字段是诚实声明(不改变后端行为)。\n protocolVersion: '4.0',\n commandId,\n conversationId,\n type: 'message.create',\n timestamp: Date.now(),\n payload: {\n content: prompt,\n agentKey: opts.agentKey,\n directOpsAgentId: opts.agentId,\n pageContext: opts.pageContext,\n attachments: opts.attachments?.length ? opts.attachments : undefined,\n },\n };\n ws.send(cmd);\n\n const events: AsyncIterable<StreamEventEnvelope> = {\n [Symbol.asyncIterator]: () => ({\n next: () =>\n new Promise((resolve) => {\n if (queue.length) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n resolve({ value: queue.shift()!, done: false });\n } else if (done) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n resolve({ value: undefined as any, done: true });\n } else {\n waiters.push(resolve);\n }\n }),\n }),\n };\n\n const cancel = (_reason: string = 'USER_CANCEL'): void => {\n try {\n // response.interrupt — 后端唯一认的打断命令(StreamCommandType.RESPONSE_INTERRUPT)。\n // 🔴 旧版发 v3.2 的 stream.cancel:后端命令枚举里没有它——cancel 一直是空操作\n // (Esc/Ctrl+C 打断踢空,2026-06-12 用户终验钉出)。统一走 buildInterrupt,消除\n // 与 control-channel 手搓 envelope 的 protocolVersion 不一致(白盒审计 RISK 文档债)。\n ws.send(buildInterrupt(conversationId));\n } catch {\n /* ws 已关闭, 忽略 */\n }\n };\n\n const onControl = (fn: (ev: ControlChannelEnvelope) => void): (() => void) =>\n ws.onControl((ev) => {\n if (ev.conversationId === conversationId) fn(ev);\n });\n\n // 回发命令(approval.* / interaction.answer)的 type 已纳入 StreamCommandType、protocolVersion\n // 收窄为 '3.0',StreamCommand 结构兼容 StreamCommandEnvelope,无需 as 转换。\n const sendCommand = (command: StreamCommand): void => ws.send(command);\n\n return { conversationId, commandId, events, cancel, onControl, sendCommand };\n}\n","/**\n * control-channel — 控制通道(channel='control')解析 + 回发命令构造。\n * 回发走 StreamCommandType(已坐实三方一致:后端 ConversationWebSocketHandler.dispatch /\n * 前端 useAIChat / 协议文档 stream-protocol.md §4),不用死枚举 control.interaction.decision。\n */\nimport { randomUUID } from 'node:crypto';\n\nexport interface InteractionRequest {\n kind: 'CONFIRM' | 'ASK';\n interactionId: string;\n summary: string;\n originAgentPath: string;\n ttlSeconds: number;\n // CONFIRM 专属\n riskLevel?: string;\n toolName?: string;\n argsPreview?: Record<string, unknown>;\n // ASK 专属\n question?: string;\n choices?: string[];\n}\n\nexport interface InteractionResolved {\n interactionId: string;\n phase: 'RESUMED' | 'TERMINAL' | string;\n}\n\nexport interface StreamCommand {\n protocolVersion: '3.0';\n commandId: string;\n conversationId: string;\n type:\n | 'approval.confirm'\n | 'approval.reject'\n | 'interaction.answer'\n // 流中途控制(P0 S-interrupt / S-steer):cooperative cancel / 注入下一 round SystemMessage\n | 'response.interrupt'\n | 'response.steer';\n timestamp: number;\n payload: Record<string, unknown>;\n}\n\nexport function parseInteractionRequest(payload: Record<string, unknown>): InteractionRequest {\n return {\n kind: payload.type as 'CONFIRM' | 'ASK',\n interactionId: payload.interactionId as string,\n summary: (payload.summary as string) || '',\n originAgentPath: (payload.originAgentPath as string) || '',\n ttlSeconds: (payload.ttlSeconds as number) || 0,\n riskLevel: payload.riskLevel as string | undefined,\n toolName: payload.toolName as string | undefined,\n argsPreview: payload.argsPreview as Record<string, unknown> | undefined,\n question: payload.question as string | undefined,\n choices: payload.choices as string[] | undefined,\n };\n}\n\nexport function parseInteractionResolved(payload: Record<string, unknown>): InteractionResolved {\n return {\n interactionId: payload.interactionId as string,\n phase: (payload.phase as string) || '',\n };\n}\n\n// timestamp 由调用点用 Date.now() 注入;测试只校验 type/conversationId/payload,不校验具体时间戳。\nfunction envelope(\n type: StreamCommand['type'],\n conversationId: string,\n payload: Record<string, unknown>,\n): StreamCommand {\n return { protocolVersion: '3.0', commandId: randomUUID(), conversationId, type, timestamp: Date.now(), payload };\n}\n\nexport function buildConfirm(confirmationId: string, conversationId: string): StreamCommand {\n return envelope('approval.confirm', conversationId, { confirmationId });\n}\n\nexport function buildReject(confirmationId: string, conversationId: string, reason?: string): StreamCommand {\n return envelope('approval.reject', conversationId, reason ? { confirmationId, reason } : { confirmationId });\n}\n\nexport function buildAnswer(\n interactionId: string,\n conversationId: string,\n answer: string,\n choiceIndex?: number,\n): StreamCommand {\n return envelope('interaction.answer', conversationId, { interactionId, answer, choiceIndex: choiceIndex ?? null });\n}\n\n/**\n * 流中途打断(cooperative cancel)。后端收到后停止当前 round,流以 ROOT message.stop\n * (payload.stopReason==\"interrupted\")终结。payload 为空对象 {}(后端契约已核实)。\n */\nexport function buildInterrupt(conversationId: string): StreamCommand {\n return envelope('response.interrupt', conversationId, {});\n}\n\n/**\n * 流中途引导(steer)。后端入队 content,下一 round 注入 SystemMessage;无任何 ack 事件\n * (v3 ack 已退役),流正常完成。payload 为 {content}(后端契约已核实)。\n */\nexport function buildSteer(content: string, conversationId: string): StreamCommand {\n return envelope('response.steer', conversationId, { content });\n}\n","/** 委派类工具名(对齐 Web constants.ts::isDelegationToolName)。hcm_handoff 为历史别名。 */\nconst DELEGATION_TOOLS = new Set(['hcm_delegate', 'hcm_handoff']);\nexport function isDelegationToolName(name: string): boolean {\n return DELEGATION_TOOLS.has(name);\n}\n","/**\n * v4-reducer — 复刻 hcm_nx_cloud useChatItemsStreamV4.ts::createV4Reducer 的 fold 状态机,\n * 但输出端换成 CLI 流式语义事件(V4RenderSink)。终端是 append-only 流,不能像 Web 那样\n * 回填 item,故 tool 行等 block.stop(args 齐)才发 onToolStarted。\n * 单层委派模型(与 Web 同限制):currentParentToolUseId 单值;多级嵌套靠 renderer depth counter。\n */\nimport { isDelegationToolName } from './delegation-constants';\nimport { truncateDisplay } from './display-width';\n\nexport interface V4RenderSink {\n onText(text: string, parentToolUseId: string | null): void;\n onThinkingDelta(text: string, parentToolUseId: string | null): void;\n onThinkingDone(parentToolUseId: string | null): void;\n onToolStarted(callId: string, toolName: string, argsPreview: string, parentToolUseId: string | null): void;\n /**\n * @param nextSteps G5 · 工具失败时从 result envelope 解析出的 ADR-058 路标(`error.nextSteps`\n * 数组 / 顶层 `next_step`)。成功或无路标时 undefined。供 host 在失败行下透出下一步指引。\n * @param toolName #1930 · 工具名。优先取 block.start 注册值;缓存短路 no-op 等无 block.start\n * 的事件由 reducer 从 result 文本兜底解析(如「hcm_describe_tool (缓存命中)」),避免 host\n * 裸显示 `?`。仍解析不出时 undefined,host 自行兜底。\n */\n onToolResult(\n callId: string,\n success: boolean,\n durMs: number,\n childConversationId: string | null,\n parentToolUseId: string | null,\n nextSteps?: string[],\n toolName?: string,\n ): void;\n onContentPart(part: Record<string, unknown>, parentToolUseId: string | null): void;\n /**\n * 用户中途纠偏在轮界被真正注入(v4 `steering.applied` 顶层事件,W1a-2)。\n * 仅注入时到达——客户端禁止在发 response.steer 时乐观本地显示「已纠偏」。\n */\n onSteeringApplied(contents: string[], round: number): void;\n onDelegationStart(agentKey: string, parentToolUseId: string): void;\n /**\n * 委派收口。由 hcm_delegate 自身的 tool.result 驱动(后端时序保证:子流 message.stop\n * 先到、父 tool.result 紧随其后,见 WebSocketRuntimeEventProjector.finishChildStreamIfAny)\n * ——因此能携带委派整体 success / 耗时 / 失败路标(2026-06-11 review C1/I1:旧版由子\n * message.stop 驱动拿不到 success,失败委派被静默渲染成功)。\n *\n * @param nextSteps 委派失败时的 ADR-058 路标;成功为 undefined。\n */\n onDelegationEnd(parentToolUseId: string, success: boolean, durMs: number, nextSteps?: string[]): void;\n /**\n * 委派「暂停待应答」(非失败):specialist 在子流内调 hcm_ask_user / 触发风险确认时,\n * 后端按 HALT 语义给 hcm_delegate 的 tool.result 发 transport `success=false`,但其\n * envelope 实为 `ok:true` + `INTERACTION_REQUIRED`/`CONFIRMATION_REQUIRED` 标记\n * (后端 InteractionSuspendMiddleware / RiskConfirmationMiddleware)。这不是失败——\n * 用户应答后委派会 RESUME(同 parentToolUseId 再来一段 message.start)续跑到真终态。\n *\n * 消费端据此:保留委派块不收口、累计本段耗时,等 RESUME 续同一块——避免把暂停\n * 渲染成红色「委派失败」、并避免暂停→续跑被计为两次委派(CLI append-only 无法回填)。\n * @param durMs 暂停前本段执行耗时(累计进委派块总耗时)。\n */\n onDelegationPaused(parentToolUseId: string, durMs: number): void;\n onTerminal(stopReason: string, tokens: number): void;\n}\n\n/**\n * 判定一条 hcm_delegate 的 tool.result(transport `success=false`)实为「暂停待应答」而非失败。\n *\n * 后端 wire 接缝不一致:HALT 时 executor 结果 `success=true`+`haltConversation`,但投影到\n * delegate 的 tool.result `success` 字段为 false;其 resultData envelope 仍诚实保留 `ok:true`\n * + summary/data 带 `INTERACTION_REQUIRED`(ASK)/`CONFIRMATION_REQUIRED`(CONFIRM)标记。\n * 以「envelope ok===true 且含交互标记」为准绳——纯结构信号,不误伤真失败(真失败 ok:false)。\n */\nexport function detectDelegationPause(resultData: unknown): boolean {\n if (!resultData || typeof resultData !== 'object') return false;\n const rd = resultData as Record<string, unknown>;\n let env: Record<string, unknown> | null = null;\n const raw = rd.result;\n if (typeof raw === 'string') {\n try {\n const o = JSON.parse(raw);\n env = o && typeof o === 'object' ? (o as Record<string, unknown>) : null;\n } catch {\n env = null;\n }\n } else if (raw && typeof raw === 'object') {\n env = raw as Record<string, unknown>;\n } else {\n env = rd;\n }\n if (!env || env.ok !== true) return false;\n const text = [env.summary, env.data].filter((x) => typeof x === 'string').join(' ');\n return text.includes('INTERACTION_REQUIRED') || text.includes('CONFIRMATION_REQUIRED');\n}\n\n/**\n * #1930 · 取 tool.result 的原始结果文本(用于无 envelope 的纯字符串结果分类)。\n *\n * wire 形态 {@code resultData = { result: \"<string>\" }}(WS ToolResultPayload);也兼容\n * resultData 本身即字符串。非字符串结果(结构化对象)返回空串——这些走正常 envelope 路径,\n * 不属本函数关心的「控制信号 / no-op 短路」纯文本类。\n */\nfunction resultText(resultData: unknown): string {\n if (typeof resultData === 'string') return resultData;\n if (resultData && typeof resultData === 'object') {\n const raw = (resultData as Record<string, unknown>).result;\n if (typeof raw === 'string') return raw;\n }\n return '';\n}\n\n/**\n * #1930 Bug B · 判定一条**直接工具**(非委派)的 tool.result 实为「待确认 / 待应答」控制信号,\n * 而非工具执行结果。\n *\n * 后端写动作命中确认门 / 交互门时,会先发一条 transport `success=false` 的 TOOL_COMPLETED,其\n * result 是纯文本前缀 {@code \"CONFIRMATION_REQUIRED: ...\"} / {@code \"INTERACTION_REQUIRED: ...\"}\n * (非 JSON envelope),同时另发一条 {@code control.interaction.request} 让 host 弹确认卡。该 tool.result\n * 对展示流是冗余的——若按普通工具渲染会变成红色 `⚙ ? ✗ 0ms` 假失败并计入失败统计(实测用户困惑)。\n *\n * 以「结果文本以这两个标记**起始**」为准绳(强协议信号,正常工具结果不会以此开头),故 host 可安全\n * 抑制该行,把待确认态交给 confirm 卡承载。委派场景的同类暂停由 {@link detectDelegationPause} 处理,\n * 二者互不重叠(委派结果是 envelope,直接工具是纯文本前缀)。\n */\nexport function detectDirectInteractionPending(resultData: unknown): boolean {\n const text = resultText(resultData).trimStart();\n return text.startsWith('CONFIRMATION_REQUIRED') || text.startsWith('INTERACTION_REQUIRED');\n}\n\n/**\n * #1930 Bug A · 为无 block.start 注册名的 tool.result 从结果文本兜底解析有意义的展示名。\n *\n * 当前唯一已知来源:ADR-095 catalog 缓存短路 no-op——后端直接发 TOOL_COMPLETED(无 TOOL_STARTED),\n * result 为 {@code \"[ADR-095 catalog cache hit] 工具 <toolName> 已在本 sub-session ...\"}。解析出工具名\n * 时返回 {@code \"<toolName> (缓存命中)\"},解析不出则返回 {@code \"缓存命中\"}。其他结果返回 undefined\n * (host 保留原 `?` 兜底给真未知事件,不强行编造名字)。\n */\nexport function deriveNamelessToolLabel(resultData: unknown): string | undefined {\n const text = resultText(resultData);\n if (text.includes('catalog cache hit')) {\n const m = text.match(/工具\\s+(\\w+)/);\n return m ? `${m[1]} (缓存命中)` : '缓存命中';\n }\n return undefined;\n}\n\n/**\n * #1949 · 从确认门 / 交互门占位结果文本解析被挂起的工具名。\n *\n * 后端 {@code RiskConfirmationMiddleware} 挂起时发的占位 TOOL_COMPLETED,其 result 为纯文本\n * {@code \"CONFIRMATION_REQUIRED: 等待用户确认 tool=<name>, confirmationId=..., scopeKey=...\"}\n * (INTERACTION_REQUIRED 亦可能带 {@code tool=})。用户确认后被 resume 的工具,其真结果\n * {@code tool.result} 可能先于(或缺失)resume 段 {@code block.start} 抵达消费端 → {@code toolNameByCallId}\n * 无注册名 → 裸显示 `?`(#1949)。这里在抑制占位结果时解析出工具名,供 resume 真结果按 callId 兜底取名。\n * 解析不出返回 undefined。\n */\nexport function parseConfirmToolName(resultData: unknown): string | undefined {\n const m = resultText(resultData).match(/\\btool=([^,\\s]+)/);\n return m ? m[1] : undefined;\n}\n\n/**\n * G5 · 从 tool.result 的 resultData 防御性提取 ADR-058 `next_step` 路标。\n *\n * 后端 wire:`resultData = { result: \"<envelope JSON 字符串>\" }`,失败 envelope 形如\n * `{ ok:false, error:{ code, message, nextSteps:[...] } }`;部分执行器平铺顶层 `next_step`\n * (可为 string / string[] / {message,...} map)。这里兼容多形态,任一解析失败都静默返回\n * undefined(非 JSON 纯文本 result 不炸)。\n */\nexport function extractNextSteps(resultData: unknown): string[] | undefined {\n if (!resultData || typeof resultData !== 'object') return undefined;\n const rd = resultData as Record<string, unknown>;\n // wire 形态:{ result: \"<json string>\" };也兼容 resultData 本身即结构化对象。\n let env: Record<string, unknown> | null = null;\n const raw = rd.result;\n if (typeof raw === 'string') {\n try {\n const o = JSON.parse(raw);\n env = o && typeof o === 'object' ? (o as Record<string, unknown>) : null;\n } catch {\n env = null;\n }\n } else if (raw && typeof raw === 'object') {\n env = raw as Record<string, unknown>;\n } else {\n env = rd;\n }\n if (!env) return undefined;\n\n const collect = (v: unknown): string[] => {\n if (Array.isArray(v)) return v.map((x) => String(x)).filter((s) => s.length > 0);\n if (typeof v === 'string' && v.length > 0) return [v];\n if (v && typeof v === 'object') {\n const m = (v as Record<string, unknown>).message;\n if (typeof m === 'string' && m.length > 0) return [m];\n }\n return [];\n };\n\n const error = env.error;\n const fromError =\n error && typeof error === 'object'\n ? collect((error as Record<string, unknown>).nextSteps).concat(\n collect((error as Record<string, unknown>).next_step),\n )\n : [];\n const fromTop = collect(env.nextSteps).concat(collect(env.next_step));\n const all = [...fromError, ...fromTop];\n return all.length > 0 ? all : undefined;\n}\n\ninterface V4BlockMeta {\n blockType: string;\n toolName?: string;\n callId?: string;\n parentToolUseId: string | null;\n jsonBuffer: string;\n}\n\nexport function createV4Reducer(sink: V4RenderSink) {\n const blockMap = new Map<number, V4BlockMeta>();\n const toolNameByCallId = new Map<string, string>();\n // #1949 · 确认门通过后被 resume 的直接工具:其 resume 段 block.start 可能未抵达消费端(只到\n // tool.result)→ toolNameByCallId 无注册名 → 裸显示 `?`。挂起时占位结果文本带 `tool=<name>`,\n // 在抑制该占位结果时按 callId 暂存,供 resume 真结果兜底取名(见 tool.result case)。\n const confirmToolNameByCallId = new Map<string, string>();\n // 嵌套委派归属栈(2026-06-11 review I2):旧单值 currentParent 在 d1 子流内再委派 d2 时,\n // d2 的 message.stop 会把归属误归零 → d1 剩余子流文本被当主流程平铺。栈语义:push on\n // 子 message.start,pop on 子 message.stop,currentParent 恒为栈顶。\n const parentStack: string[] = [];\n let currentParent: string | null = null;\n\n const parentOf = (payloadParent?: unknown): string | null =>\n (typeof payloadParent === 'string' && payloadParent) ? payloadParent : currentParent;\n\n // 按显示宽度截断(CJK=2 列):单 value ≤40 列防 reasoning 类长参数霸行,总预览 ≤96 列\n // 保证「⚙ name preview ✓ Xms」在 120 列内单行收口(旧按字符数 slice(120) 中文实占 240 列必折行)。\n function buildArgsPreview(args: Record<string, unknown>): string {\n const preview = Object.entries(args)\n .map(([k, v]) => {\n const raw = typeof v === 'object' ? JSON.stringify(v) : String(v);\n return `${k}=${truncateDisplay(raw, 40)}`;\n })\n .join(' · ');\n return truncateDisplay(preview, 96);\n }\n function parseJson(s: string): Record<string, unknown> | null {\n try { const o = JSON.parse(s); return o && typeof o === 'object' ? o : null; } catch { return null; }\n }\n\n function handle(type: string, payload: Record<string, unknown>): boolean {\n switch (type) {\n case 'message.start': {\n const p = payload.parentToolUseId;\n if (typeof p === 'string' && p) {\n parentStack.push(p);\n currentParent = p;\n sink.onDelegationStart((payload.agentKey as string) || 'specialist', p);\n } else {\n // ROOT message:归属复位(resume 等场景防御性清栈)。\n parentStack.length = 0;\n currentParent = null;\n }\n return true;\n }\n case 'block.start': {\n const index = payload.index as number;\n const blockType = (payload.blockType as string) || 'text';\n const toolName = (payload.toolName as string) || undefined;\n const callId = (payload.callId as string) || undefined;\n blockMap.set(index, { blockType, toolName, callId, parentToolUseId: parentOf(payload.parentToolUseId), jsonBuffer: '' });\n if (blockType === 'tool_use' && callId && toolName) toolNameByCallId.set(callId, toolName);\n // CLI:tool 行等 block.stop(args 齐)才发 onToolStarted;此处不输出。\n return true;\n }\n case 'block.delta': {\n const index = payload.index as number;\n const meta = blockMap.get(index);\n const parent = meta?.parentToolUseId ?? currentParent;\n switch (payload.deltaType) {\n case 'text_delta': sink.onText((payload.text as string) || '', parent); return true;\n case 'thinking_delta': sink.onThinkingDelta((payload.text as string) || '', parent); return true;\n case 'input_json_delta': if (meta) meta.jsonBuffer += (payload.partialJson as string) || ''; return true;\n case 'content_part': if (meta) meta.jsonBuffer += (payload.partialJson as string) || ''; return true;\n default: return true;\n }\n }\n case 'block.stop': {\n const index = payload.index as number;\n const meta = blockMap.get(index);\n blockMap.delete(index);\n if (!meta) return true;\n if (meta.blockType === 'thinking') { sink.onThinkingDone(meta.parentToolUseId); return true; }\n if (meta.blockType === 'tool_use' && meta.callId) {\n const args = meta.jsonBuffer ? parseJson(meta.jsonBuffer) ?? {} : {};\n if (meta.toolName && isDelegationToolName(meta.toolName)) return true; // delegation 由 message.start / tool.result 渲染\n sink.onToolStarted(meta.callId, meta.toolName || '?', buildArgsPreview(args), meta.parentToolUseId);\n return true;\n }\n if (meta.blockType === 'content_part' && meta.jsonBuffer) {\n const part = parseJson(meta.jsonBuffer)?.part;\n if (part && typeof part === 'object') {\n const p = part as Record<string, unknown>;\n // fileDownload content_part({fileName, downloadUrl})→ 渲染为可点的绝对签名链接,\n // 点击由浏览器打开(ADR-140,免 Bearer);CLI 不再自动落盘(用户要求点击打开即可)。\n sink.onContentPart(p, meta.parentToolUseId);\n }\n return true;\n }\n return true;\n }\n case 'tool.result': {\n const callId = payload.callId as string;\n if (!callId) return true;\n const toolName = toolNameByCallId.get(callId);\n const success = payload.success !== false;\n // G5:失败才解析 next_step 路标(成功路径不解析,省开销)。\n const nextSteps = success ? undefined : extractNextSteps(payload.resultData);\n if (toolName && isDelegationToolName(toolName)) {\n // 委派收口在此驱动(review C1/I1):本事件带委派整体 success/durMs/失败路标,\n // 失败委派不再被静默渲染成功。子流 message.stop 仅做归属弹栈(见 message.stop case)。\n const durMs = (payload.executionTimeMs as number) || 0;\n // transport success=false 但实为「暂停待应答」(envelope ok:true + 交互标记)→ 非失败,\n // 走 onDelegationPaused 保留委派块等 RESUME 续跑;真失败仍走 onDelegationEnd。\n if (!success && detectDelegationPause(payload.resultData)) {\n // 🔴 不 delete toolNameByCallId[callId]:RESUME 后的「真收口」tool.result(同 callId)\n // 仍须被识别为委派——否则它退化成普通 onToolResult,渲染成神秘「⚙ ? ✓ 0ms」工具行。\n sink.onDelegationPaused(callId, durMs);\n } else {\n toolNameByCallId.delete(callId);\n sink.onDelegationEnd(callId, success, durMs, nextSteps);\n }\n return true;\n }\n toolNameByCallId.delete(callId);\n // #1930 Bug B:直接工具的「待确认 / 待应答」控制信号(CONFIRMATION_REQUIRED /\n // INTERACTION_REQUIRED 纯文本前缀)不是工具结果——它对应一条 control.interaction.request,\n // 由 confirm 卡承载可见待确认态。这里抑制,避免渲染成红色 `⚙ ? ✗ 0ms` 假失败 + 计入失败统计。\n if (detectDirectInteractionPending(payload.resultData)) {\n // #1949 · 暂存被挂起工具名,供其被 resume 后的真结果兜底取名(resume 段 block.start\n // 可能未抵达消费端 → 否则 resume 工具行裸显示 `?`)。\n const confirmName = parseConfirmToolName(payload.resultData);\n if (confirmName) confirmToolNameByCallId.set(callId, confirmName);\n return true;\n }\n // #1930 Bug A + #1949:名字优先取 block.start 注册值;缺失时取确认门暂存名(resume 直接工具);\n // 再退回结果文本兜底(缓存短路 no-op);都没有才让 host 显示 `?`。\n const resolvedToolName =\n toolName ?? confirmToolNameByCallId.get(callId) ?? deriveNamelessToolLabel(payload.resultData);\n confirmToolNameByCallId.delete(callId);\n sink.onToolResult(\n callId,\n success,\n (payload.executionTimeMs as number) || 0,\n (payload.childConversationId as string) || null,\n parentOf(payload.parentToolUseId),\n nextSteps,\n resolvedToolName,\n );\n return true;\n }\n case 'steering.applied': {\n const contents = Array.isArray(payload.contents)\n ? (payload.contents as unknown[]).map((c) => String(c)).filter((c) => c.length > 0)\n : [];\n if (contents.length > 0) {\n sink.onSteeringApplied(contents, (payload.round as number) ?? 0);\n }\n return true;\n }\n case 'message.stop': {\n const p = payload.parentToolUseId;\n if (typeof p === 'string' && p) {\n // 子流结束:仅弹栈回外层归属(嵌套委派 d1>d2 时 d2 结束回 d1)。收口渲染由\n // 紧随其后的 delegation tool.result 驱动(带 success,见 tool.result case)。\n const idx = parentStack.lastIndexOf(p);\n if (idx >= 0) parentStack.splice(idx, 1);\n currentParent = parentStack[parentStack.length - 1] ?? null;\n return true;\n }\n parentStack.length = 0;\n currentParent = null;\n blockMap.clear();\n sink.onTerminal((payload.stopReason as string) || 'end_turn', (payload.totalTokens as number) || 0);\n return true;\n }\n default: return false;\n }\n }\n\n return { handle };\n}\n","import type { WsClient } from './ws-client';\nimport { sendMessageAndStream, type SendMessageOpts } from './stream-iterator';\nimport type { StreamEventEnvelope, ResponseFailedPayload } from './protocol';\nimport { createV4Reducer, type V4RenderSink } from './v4-reducer';\nimport { CliError, CliErrorCode } from '../errors';\n\nexport interface OneShotToolCall {\n /** 工具名;委派为 'hcm_delegate'。无法解析时 '?'。 */\n name: string;\n /** 参数预览(reducer 已按显示宽度截断);委派时为目标 agentKey。 */\n argsPreview: string;\n success: boolean;\n elapsedMs: number;\n /** 委派调用标记。 */\n isDelegation?: boolean;\n /** 归属父 tool_use(顶层直接工具为 null)。 */\n parentToolUseId: string | null;\n}\n\nexport interface OneShotResult {\n conversationId: string;\n streamId: string;\n traceId: string | null;\n /** 最终助手回答正文(ROOT 段文本;委派 specialist 的中间文本不计入)。 */\n content: string;\n toolCalls: OneShotToolCall[];\n elapsedMs: number;\n events: StreamEventEnvelope[];\n /** 终态原因(v4 message.stop.stopReason);未见终态时 undefined。 */\n stopReason?: string;\n /** 本轮总 token(v4 message.stop.totalTokens)。 */\n tokens?: number;\n}\n\nexport interface OneShotOpts extends SendMessageOpts {\n collectEvents?: boolean;\n}\n\n/**\n * 非流式单发:消费 v4 事件流并聚合出最终结果。\n *\n * 🔴 与流式路径同源:把事件喂进 {@link createV4Reducer}(与 CLI streaming、Web 端 fold\n * 状态机一致的 v4 语义层),用一个 headless sink 累加 ROOT 文本 + 收集工具调用——而非\n * 手搓第二套聚合。历史 bug:旧实现监听已废弃的 v3 事件名(response.part.delta /\n * response.completed / response.tool.*),后端早改发 v4(block.delta:text_delta /\n * message.stop)→ switch 全不命中 → content 恒空(--no-stream / --output json 拿到空答案)。\n * 语义层复用见 ADR-129 D2:只换「画」的层,聚合逻辑不重复实现。\n *\n * 终态由 stream-iterator 在 ROOT message.stop(或断连)关闭 async iterator 驱动,本函数\n * 无需自判终止。v3 残余 `response.failed`(仅老路径出现)仍显式抛 CliError 保留旧语义。\n */\nexport async function oneShot(\n ws: WsClient,\n prompt: string,\n opts: OneShotOpts = {},\n): Promise<OneShotResult> {\n const t0 = Date.now();\n const { conversationId, events } = await sendMessageAndStream(ws, prompt, opts);\n\n let content = '';\n let streamId = '';\n let traceId: string | null = null;\n let stopReason: string | undefined;\n let tokens: number | undefined;\n const toolCalls: OneShotToolCall[] = [];\n const collected: StreamEventEnvelope[] = [];\n\n // reducer sink 无 block.start→tool.result 的 callId 内联参数,故按 callId 暂存预览 /\n // 委派 agentKey,待 result / delegationEnd 到达时回填。\n const argsPreviewByCallId = new Map<string, string>();\n const agentKeyByCallId = new Map<string, string>();\n\n const sink: V4RenderSink = {\n // ROOT 段文本才是最终回答;specialist 子流文本(parent 非空)是中间产物,不计入 content。\n onText(text, parentToolUseId) {\n if (parentToolUseId === null) content += text;\n },\n onThinkingDelta() {},\n onThinkingDone() {},\n onToolStarted(callId, _toolName, argsPreview) {\n argsPreviewByCallId.set(callId, argsPreview);\n },\n onToolResult(callId, success, durMs, _childConversationId, parentToolUseId, _nextSteps, toolName) {\n toolCalls.push({\n name: toolName ?? '?',\n argsPreview: argsPreviewByCallId.get(callId) ?? '',\n success,\n elapsedMs: durMs,\n parentToolUseId,\n });\n argsPreviewByCallId.delete(callId);\n },\n onContentPart() {},\n onSteeringApplied() {},\n onDelegationStart(agentKey, parentToolUseId) {\n agentKeyByCallId.set(parentToolUseId, agentKey);\n },\n onDelegationEnd(callId, success, durMs) {\n toolCalls.push({\n name: 'hcm_delegate',\n argsPreview: agentKeyByCallId.get(callId) ?? '',\n success,\n elapsedMs: durMs,\n isDelegation: true,\n parentToolUseId: null,\n });\n agentKeyByCallId.delete(callId);\n },\n onDelegationPaused() {},\n onTerminal(reason, tk) {\n stopReason = reason;\n tokens = tk;\n },\n };\n\n const reducer = createV4Reducer(sink);\n\n for await (const ev of events) {\n if (opts.collectEvents) collected.push(ev);\n if (!streamId) streamId = ev.streamId;\n if (!traceId) traceId = ev.traceId;\n\n // v3 残余终态:仅老后端路径出现,v4 无此事件。保留旧语义——失败即抛。\n if (ev.type === 'response.failed') {\n throw new CliError({\n code: CliErrorCode.BUSINESS_RULE_VIOLATION,\n message: (ev.payload as ResponseFailedPayload).message,\n traceId: traceId ?? undefined,\n });\n }\n\n reducer.handle(ev.type, ev.payload as Record<string, unknown>);\n }\n\n return {\n conversationId,\n streamId,\n traceId,\n content,\n toolCalls,\n elapsedMs: Date.now() - t0,\n events: collected,\n stopReason,\n tokens,\n };\n}\n","import * as fs from 'node:fs/promises';\nimport { conversationStateFile, profileDir } from '../config/paths';\n\nexport interface ConversationState {\n lastConversationId?: string;\n lastStreamId?: string;\n lastTraceId?: string;\n lastAgentKey?: string;\n updatedAt: string;\n}\n\nexport async function loadConversationState(profile: string): Promise<ConversationState | null> {\n try {\n const raw = await fs.readFile(conversationStateFile(profile), 'utf-8');\n return JSON.parse(raw) as ConversationState;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw e;\n }\n}\n\nexport async function saveConversationState(\n profile: string,\n state: ConversationState,\n): Promise<void> {\n await fs.mkdir(profileDir(profile), { recursive: true });\n await fs.writeFile(\n conversationStateFile(profile),\n JSON.stringify({ ...state, updatedAt: new Date().toISOString() }, null, 2),\n );\n}\n","import type { AxiosInstance } from 'axios';\n\n/** Default analyst agent id (seeded in HCM at tenant=__SYSTEM__). */\nexport const DEFAULT_AGENT_ID = '019cca00-0000-7000-0000-000000000001';\n\nexport interface CreatedConversation {\n id: string;\n agentId: string;\n title?: string;\n}\n\n/**\n * Create a new conversation in HCM. Required before sending any /ws/conversation\n * message — the WS handler looks up conversation→agentId in DB.\n */\nexport async function createConversation(\n http: AxiosInstance,\n agentId: string = DEFAULT_AGENT_ID,\n): Promise<CreatedConversation> {\n const resp = await http.post('/api/models/ConversationModel', { agentId });\n return {\n id: resp.data.id,\n agentId: resp.data.agentId,\n title: resp.data.title,\n };\n}\n","import type { AxiosInstance } from 'axios';\nimport { truncateDisplay } from './display-width';\n\n/**\n * 续会话回放(CLI 做透轮刀⑤,对标 Claude Code resume 的上下文可见性)。\n *\n * hcm chat 只有显式 --resume 续接 conversation 时才回放历史;否则默认开新会话。\n * 本模块拉\n * timeline(GET /api/models/ConversationModel/{id}/action/timeline,与 Web 回放\n * 同源)折出轻量摘要:最近 N 轮 user/assistant 文本 + 更早轮次计数。\n */\n\n/** timeline 条目(后端 TimelineItemDto 的 CLI 侧投影,只取回放需要的字段)。 */\nexport interface TimelineItemLite {\n kind: string;\n seq: number;\n /** user-msg / steering 的原文。 */\n text?: string | null;\n /** chat-text 的正文。 */\n content?: string | null;\n /** user-msg 附件元数据(#1849 journal 单源透传;issue #64 回放 📎)。 */\n attachments?: Array<{ fileName?: string }> | null;\n}\n\nexport interface ResumeTurn {\n role: 'user' | 'assistant';\n text: string;\n}\n\nexport interface ResumeSummary {\n conversationId: string;\n /** 会话总轮数(user-msg 条目数)。 */\n totalRounds: number;\n /** 最近 maxRounds 轮的 user/assistant 文本(已按显示宽度截断为单行摘要)。 */\n recent: ResumeTurn[];\n}\n\nfunction normalizeTimelineItems(body: unknown): TimelineItemLite[] | null {\n if (Array.isArray(body)) return body as TimelineItemLite[];\n if (\n body\n && typeof body === 'object'\n && Array.isArray((body as { data?: unknown }).data)\n ) {\n return (body as { data: TimelineItemLite[] }).data;\n }\n return null;\n}\n\n/**\n * 拉取 conversation timeline。任何失败(网络/404/结构异常)返回 null——\n * 回放是体验增强,绝不阻断聊天主流程。\n */\nexport async function fetchConversationTimeline(\n http: AxiosInstance,\n conversationId: string,\n): Promise<TimelineItemLite[] | null> {\n try {\n const resp = await http.get(\n `/api/models/ConversationModel/${conversationId}/action/timeline`);\n return normalizeTimelineItems(resp.data);\n } catch {\n return null;\n }\n}\n\n/**\n * 严格拉取 conversation timeline。用于 CLI 显式 inspect 场景:网络/404/结构异常都应暴露,\n * 不能像 resume 体验增强那样静默吞掉。\n */\nexport async function fetchConversationTimelineStrict(\n http: AxiosInstance,\n conversationId: string,\n): Promise<TimelineItemLite[]> {\n const resp = await http.get(\n `/api/models/ConversationModel/${conversationId}/action/timeline`);\n const items = normalizeTimelineItems(resp.data);\n if (!items) {\n throw new Error(`Conversation timeline response is not an array: ${conversationId}`);\n }\n return items;\n}\n\n/**\n * timeline → 续会话摘要。取最近 maxRounds 轮:每轮 = user-msg 原文 + 该轮最后一条\n * chat-text 正文(中间 tools/delegation/steering 等过程条目不进摘要——回顾要的是\n * 「聊到哪了」不是过程重放)。文本截为单行(换行折叠 + 显示宽度截断)。\n */\nexport function buildResumeSummary(\n conversationId: string,\n items: TimelineItemLite[],\n maxRounds: number = 2,\n maxWidth: number = 76,\n): ResumeSummary | null {\n if (!Array.isArray(items) || items.length === 0) return null;\n // 按 user-msg 切轮:每个 user-msg 开启一轮,轮内最后一条 chat-text 是该轮 assistant 收尾。\n interface Round { user: string; assistant: string | null }\n const rounds: Round[] = [];\n for (const it of items) {\n if (it.kind === 'user-msg' && it.text && it.text.trim()) {\n const atts = (it.attachments ?? []).map((a) => a?.fileName).filter(Boolean);\n const suffix = atts.length ? ` 📎 ${atts.join(' · ')}` : '';\n rounds.push({ user: it.text + suffix, assistant: null });\n } else if (it.kind === 'chat-text' && it.content && it.content.trim() && rounds.length > 0) {\n const last = rounds[rounds.length - 1];\n if (last) last.assistant = it.content;\n }\n }\n if (rounds.length === 0) return null;\n\n const oneLine = (s: string): string =>\n truncateDisplay(s.replace(/\\s*\\n\\s*/g, ' ').trim(), maxWidth);\n\n const recent: ResumeTurn[] = [];\n for (const r of rounds.slice(-maxRounds)) {\n recent.push({ role: 'user', text: oneLine(r.user) });\n if (r.assistant) recent.push({ role: 'assistant', text: oneLine(r.assistant) });\n }\n return { conversationId, totalRounds: rounds.length, recent };\n}\n\nexport function findLastAssistantSeq(items: TimelineItemLite[] | null | undefined): number | null {\n const last = [...(items ?? [])].reverse()\n .find((it) => it.kind === 'chat-text' && Number.isInteger(it.seq));\n return last?.seq ?? null;\n}\n","import type { AxiosInstance } from 'axios';\nimport { query } from '../models/query';\n\nexport interface ConversationListItem {\n id?: string;\n title?: string | null;\n summary?: string | null;\n agentId?: string | null;\n messageCount?: number | null;\n totalTokens?: number | null;\n lastActiveAt?: string | null;\n lastMessagePreview?: string | null;\n createTime?: string | null;\n updateTime?: string | null;\n}\n\nexport interface ConversationListResult {\n rows: ConversationListItem[];\n total?: number;\n}\n\nexport async function fetchRecentConversations(\n http: AxiosInstance,\n limit: number = 10,\n): Promise<ConversationListResult> {\n const result = await query<ConversationListItem>(http, 'ConversationModel', {\n filter: { origin: 'USER_CHAT', archived: false },\n fields: [\n 'id',\n 'title',\n 'summary',\n 'agentId',\n 'messageCount',\n 'totalTokens',\n 'lastActiveAt',\n 'lastMessagePreview',\n 'createTime',\n 'updateTime',\n ],\n sort: [{ field: 'lastActiveAt', direction: 'desc' }],\n limit,\n });\n return { rows: result.rows, total: result.total };\n}\n","export const MINI_CONTEXT_FILE = 'hcm.context.json';\nexport const MINI_STATE_DIR = '.hcm';\nexport const MINI_PULL_MANIFEST_FILE = 'mini-manifest.json';\nexport const MINI_REMOTE_ROOT = 'mini_apps';\nexport const MINI_CONTEXT_KIND = 'hcm-mini-app';\nexport const MINI_CONTEXT_VERSION = 1;\n\nexport type MiniSurface = 'embedded' | 'standalone';\nexport type MiniTemplateKind = 'dashboard' | 'multi-page' | 'writable-workbench';\n\nexport interface WorkspaceFileItem {\n id: string;\n workspaceKey: string;\n relativePath: string;\n fileType?: string;\n fileSize?: number;\n lastModified?: number | string | null;\n}\n\nexport interface WorkspaceFileContent {\n workspaceKey: string;\n relativePath: string;\n content: string;\n fileType?: string;\n}\n\nexport interface WorkspaceFileListResult {\n data: WorkspaceFileItem[];\n total: number;\n}\n\nexport interface MiniProjectContext {\n kind: typeof MINI_CONTEXT_KIND;\n version: typeof MINI_CONTEXT_VERSION;\n appCode: string;\n workspaceKey: string;\n remotePrefix: string;\n tenantId?: string;\n endpoint?: string;\n profile?: string;\n updatedAt: string;\n}\n\nexport interface MiniFileSnapshot {\n localPath: string;\n remotePath: string;\n fileId?: string;\n sha256: string;\n size: number;\n lastModified?: number | string | null;\n}\n\nexport interface MiniPullManifest {\n version: typeof MINI_CONTEXT_VERSION;\n appCode: string;\n workspaceKey: string;\n remotePrefix: string;\n profile?: string;\n pulledAt: string;\n files: MiniFileSnapshot[];\n}\n\nexport interface MiniLocalFile {\n localPath: string;\n absolutePath: string;\n content: string;\n sha256: string;\n size: number;\n}\n\nexport type MiniLocalStatus = 'added' | 'modified' | 'deleted' | 'unchanged';\n\nexport interface MiniStatusEntry {\n localPath: string;\n remotePath: string;\n status: MiniLocalStatus;\n fileId?: string;\n sha256?: string;\n baseSha256?: string;\n}\n\nexport interface MiniValidationResult {\n valid: boolean;\n appCode?: string;\n workspaceKey?: string;\n errors: string[];\n warnings: string[];\n}\n\nexport interface MiniPullResult {\n appCode: string;\n workspaceKey: string;\n targetDir: string;\n files: MiniFileSnapshot[];\n}\n\nexport type MiniPushOperation = 'create' | 'update' | 'delete' | 'skip';\n\nexport interface MiniPushPlanItem {\n operation: MiniPushOperation;\n localPath: string;\n remotePath: string;\n fileId?: string;\n reason?: string;\n}\n\nexport interface MiniPushConflict {\n localPath: string;\n remotePath: string;\n reason: string;\n}\n\nexport interface MiniPushResult {\n appCode: string;\n workspaceKey: string;\n dryRun: boolean;\n operations: MiniPushPlanItem[];\n conflicts: MiniPushConflict[];\n}\n\nexport interface MiniPreviewTarget {\n key: 'bare' | 'standalone' | 'embedded';\n name: string;\n url: string;\n}\n\nexport type MiniSmokeTargetKind = 'preview' | 'manifest' | 'asset';\n\nexport interface MiniSmokeTargetResult {\n kind: MiniSmokeTargetKind;\n surface?: MiniPreviewTarget['key'];\n url: string;\n status: number | null;\n ok: boolean;\n error?: string;\n}\n\nexport interface MiniSmokeResult {\n ok: boolean;\n checked: number;\n passed: number;\n failed: number;\n targets: MiniSmokeTargetResult[];\n}\n\nexport type MiniVerifyNextAction =\n | 'push'\n | 'fix-validation'\n | 'resolve-conflict'\n | 'inspect-status'\n | 'inspect-preview';\n\nexport interface MiniVerifyPlan {\n operations: MiniPushPlanItem[];\n conflicts: MiniPushConflict[];\n}\n\nexport interface MiniVerifyReport {\n ok: boolean;\n validate: MiniValidationResult;\n status: MiniStatusEntry[];\n plan: MiniVerifyPlan | null;\n planSkipped?: string;\n preview: MiniPreviewTarget[];\n smoke?: MiniSmokeResult | null;\n}\n\nexport interface MiniVerifySummary {\n validate: {\n ok: boolean;\n errors: number;\n warnings: number;\n appCode?: string;\n workspaceKey?: string;\n };\n status: {\n total: number;\n changed: number;\n added: number;\n modified: number;\n deleted: number;\n unchanged: number;\n };\n dryRun: {\n ok: boolean;\n skipped: boolean;\n skippedReason?: string;\n operations: number;\n creates: number;\n updates: number;\n deletes: number;\n skips: number;\n conflicts: number;\n };\n preview: {\n ok: boolean;\n count: number;\n surfaces: MiniPreviewTarget['key'][];\n };\n}\n\nexport interface MiniVerifyChangedFile {\n path: string;\n status: MiniLocalStatus;\n remotePath: string;\n}\n\nexport interface MiniVerifyPreviewUrl {\n surface: MiniPreviewTarget['key'];\n name: string;\n url: string;\n}\n\nexport interface MiniVerifyBrowserAcceptance {\n recommendedUrl?: string;\n expectedTexts: string[];\n expectedNetwork: string[];\n console: {\n failOn: Array<'error' | 'warn'>;\n };\n}\n\nexport interface MiniVerifyAgentReport {\n ok: boolean;\n safeToPush: boolean;\n nextAction: MiniVerifyNextAction;\n summary: MiniVerifySummary;\n changedFiles: MiniVerifyChangedFile[];\n conflicts: MiniPushConflict[];\n previewUrls: MiniVerifyPreviewUrl[];\n browserAcceptance: MiniVerifyBrowserAcceptance;\n smoke: MiniSmokeResult | null;\n}\n","import type { AxiosInstance } from 'axios';\nimport type { WorkspaceFileContent, WorkspaceFileItem, WorkspaceFileListResult } from './types';\n\nconst MODEL = 'TenantExtensionWorkspaceFile';\n\nexport async function listWorkspaceFiles(\n http: AxiosInstance,\n workspaceKey: string,\n options: { pageSize?: number } = {},\n): Promise<WorkspaceFileListResult> {\n const resp = await http.get(`/api/models/${MODEL}`, {\n params: {\n state: 'workspace',\n filter: `workspaceKey:eq:${workspaceKey}`,\n page: 0,\n size: options.pageSize ?? 1000,\n order: 'relativePath:asc',\n },\n });\n const content = Array.isArray(resp.data?.content) ? resp.data.content : [];\n return {\n data: content.map(toWorkspaceFileItem),\n total: numberOr(resp.data?.totalElements, content.length),\n };\n}\n\nexport async function loadWorkspaceFileContent(\n http: AxiosInstance,\n fileId: string,\n): Promise<WorkspaceFileContent> {\n const resp = await http.get(`/api/models/${MODEL}/${encodeURIComponent(fileId)}/action/load-content`);\n return toWorkspaceFileContent(unwrapActionData(resp.data));\n}\n\nexport async function saveWorkspaceFileContent(\n http: AxiosInstance,\n fileId: string,\n content: string,\n): Promise<WorkspaceFileItem> {\n const resp = await http.post(\n `/api/models/${MODEL}/${encodeURIComponent(fileId)}/action/save-content`,\n { form: { content } },\n );\n return toWorkspaceFileItem(unwrapActionData(resp.data));\n}\n\nexport async function createWorkspaceFile(\n http: AxiosInstance,\n workspaceKey: string,\n relativePath: string,\n content: string,\n): Promise<WorkspaceFileItem> {\n const resp = await http.post(`/api/models/${MODEL}`, {\n workspaceKey,\n relativePath,\n content,\n });\n return toWorkspaceFileItem(resp.data);\n}\n\nexport async function deleteWorkspaceFile(http: AxiosInstance, fileId: string): Promise<void> {\n await http.delete(`/api/models/${MODEL}/${encodeURIComponent(fileId)}`);\n}\n\nfunction unwrapActionData(value: unknown): unknown {\n if (value && typeof value === 'object' && 'data' in value) {\n return (value as { data?: unknown }).data;\n }\n return value;\n}\n\nfunction toWorkspaceFileItem(value: unknown): WorkspaceFileItem {\n const obj = asRecord(value);\n return {\n id: stringOr(obj.id),\n workspaceKey: stringOr(obj.workspaceKey),\n relativePath: stringOr(obj.relativePath),\n fileType: stringOrUndefined(obj.fileType),\n fileSize: numberOr(obj.fileSize, 0),\n lastModified: primitiveOrNull(obj.lastModified),\n };\n}\n\nfunction toWorkspaceFileContent(value: unknown): WorkspaceFileContent {\n const obj = asRecord(value);\n return {\n workspaceKey: stringOr(obj.workspaceKey),\n relativePath: stringOr(obj.relativePath),\n content: stringOr(obj.content),\n fileType: stringOrUndefined(obj.fileType),\n };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};\n}\n\nfunction stringOr(value: unknown): string {\n return typeof value === 'string' ? value : value == null ? '' : String(value);\n}\n\nfunction stringOrUndefined(value: unknown): string | undefined {\n return value == null ? undefined : stringOr(value);\n}\n\nfunction numberOr(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n}\n\nfunction primitiveOrNull(value: unknown): number | string | null {\n if (typeof value === 'number' || typeof value === 'string') return value;\n return null;\n}\n","import * as crypto from 'node:crypto';\nimport type { Dirent } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { AxiosInstance } from 'axios';\nimport { CliError, CliErrorCode } from '../errors';\nimport {\n createWorkspaceFile,\n deleteWorkspaceFile,\n listWorkspaceFiles,\n loadWorkspaceFileContent,\n saveWorkspaceFileContent,\n} from './workspace-files';\nimport {\n MINI_CONTEXT_FILE,\n MINI_CONTEXT_KIND,\n MINI_CONTEXT_VERSION,\n MINI_PULL_MANIFEST_FILE,\n MINI_REMOTE_ROOT,\n MINI_STATE_DIR,\n type MiniFileSnapshot,\n type MiniPushOperation,\n type MiniLocalFile,\n type MiniPreviewTarget,\n type MiniProjectContext,\n type MiniPullManifest,\n type MiniPullResult,\n type MiniSmokeResult,\n type MiniSmokeTargetResult,\n type MiniVerifyAgentReport,\n type MiniVerifyBrowserAcceptance,\n type MiniVerifyChangedFile,\n type MiniVerifyNextAction,\n type MiniVerifyPreviewUrl,\n type MiniVerifyReport,\n type MiniPushConflict,\n type MiniPushPlanItem,\n type MiniPushResult,\n type MiniStatusEntry,\n type MiniSurface,\n type MiniTemplateKind,\n type MiniValidationResult,\n type WorkspaceFileContent,\n type WorkspaceFileItem,\n} from './types';\nimport {\n buildMiniAppTemplateFiles as buildMiniAppTemplateFileSet,\n defaultMiniRequiredScopes,\n} from './templates';\n\nconst MINI_APP_CODE_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst SCOPE_PATTERN = /^[A-Z][A-Za-z0-9_]*\\.[a-z][A-Za-z0-9]*$/;\nconst SEMVER_PATTERN =\n /^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$/;\nconst TOP_LEVEL_MANIFEST_KEYS = new Set([\n 'schemaVersion',\n 'code',\n 'name',\n 'version',\n 'sdkVersion',\n 'surfaces',\n 'defaultSurface',\n 'entry',\n 'bundleObjectRef',\n 'requiredScopes',\n]);\nconst ENTRY_KEYS = new Set(['html', 'css', 'js']);\nconst EXCLUDED_DIRS = new Set(['.git', MINI_STATE_DIR, 'node_modules', 'dist']);\nconst MINI_SDK_SCOPE_METHODS: Record<string, string> = {\n query: 'list',\n get: 'get',\n create: 'create',\n update: 'update',\n remove: 'delete',\n};\nconst MINI_MODEL_CONST_PATTERN = /\\bconst\\s+([A-Z][A-Za-z0-9_]*)\\s*=\\s*['\"]([A-Z][A-Za-z0-9_]*)['\"]\\s*;/g;\nconst MINI_SDK_MODEL_CALL_PATTERN =\n /\\bhcm\\.(query|get|create|update|remove)\\s*\\(\\s*\\{[\\s\\S]*?\\bmodel\\s*:\\s*(?:['\"]([A-Z][A-Za-z0-9_]*)['\"]|([A-Z][A-Za-z0-9_]*))/g;\nconst MINI_SDK_ACTION_CALL_PATTERN =\n /\\bhcm\\.action\\s*\\(\\s*\\{[\\s\\S]*?\\bmodel\\s*:\\s*(?:['\"]([A-Z][A-Za-z0-9_]*)['\"]|([A-Z][A-Za-z0-9_]*))[\\s\\S]*?\\baction\\s*:\\s*['\"]([a-z][A-Za-z0-9]*)['\"]/g;\n\nexport interface MiniInitArgs {\n appCode: string;\n workspaceKey: string;\n targetDir: string;\n name?: string;\n defaultSurface?: MiniSurface;\n templateKind?: MiniTemplateKind;\n writableModel?: string;\n writableFields?: string[] | null;\n writableDefaults?: Record<string, unknown>;\n writableTodayDefaultFields?: string[];\n requiredScopes?: string[];\n tenantId?: string;\n endpoint?: string;\n profile?: string;\n force?: boolean;\n}\n\nexport interface MiniPullArgs {\n http: AxiosInstance;\n appCode: string;\n workspaceKey: string;\n targetDir: string;\n tenantId?: string;\n endpoint?: string;\n profile?: string;\n force?: boolean;\n}\n\nexport interface MiniPushArgs {\n http: AxiosInstance;\n rootDir: string;\n dryRun?: boolean;\n}\n\nexport async function initMiniAppProject(args: MiniInitArgs): Promise<MiniPullResult> {\n const appCode = normalizeAppCode(args.appCode);\n const workspaceKey = normalizeWorkspaceKey(args.workspaceKey);\n const targetDir = path.resolve(args.targetDir);\n const remotePrefix = miniRemotePrefix(appCode);\n const templateKind = args.templateKind ?? 'dashboard';\n const files = buildMiniAppTemplateFiles({\n appCode,\n name: args.name,\n defaultSurface: args.defaultSurface ?? 'embedded',\n templateKind,\n writableModel: args.writableModel,\n writableFields: args.writableFields,\n writableDefaults: args.writableDefaults,\n writableTodayDefaultFields: args.writableTodayDefaultFields,\n requiredScopes: args.requiredScopes?.length\n ? args.requiredScopes\n : defaultMiniRequiredScopes(templateKind, { model: args.writableModel, fields: args.writableFields }),\n tenantId: args.tenantId ?? '1',\n });\n\n await ensureTargetWritable(\n targetDir,\n files.map((file) => file.localPath).concat([MINI_CONTEXT_FILE]),\n !!args.force,\n );\n await fs.mkdir(targetDir, { recursive: true });\n for (const file of files) {\n await writeText(path.join(targetDir, file.localPath), file.content);\n }\n\n const context: MiniProjectContext = {\n kind: MINI_CONTEXT_KIND,\n version: MINI_CONTEXT_VERSION,\n appCode,\n workspaceKey,\n remotePrefix,\n tenantId: args.tenantId,\n endpoint: args.endpoint,\n profile: args.profile,\n updatedAt: new Date().toISOString(),\n };\n await writeJson(path.join(targetDir, MINI_CONTEXT_FILE), context);\n\n const localFiles = await readLocalMiniFiles(targetDir);\n await writeMiniState(targetDir, context, [], new Map());\n return {\n appCode,\n workspaceKey,\n targetDir,\n files: localFiles.map((file) => toSnapshot(context, file)),\n };\n}\n\nexport async function pullMiniAppProject(args: MiniPullArgs): Promise<MiniPullResult> {\n const appCode = normalizeAppCode(args.appCode);\n const workspaceKey = normalizeWorkspaceKey(args.workspaceKey);\n const targetDir = path.resolve(args.targetDir);\n const remotePrefix = miniRemotePrefix(appCode);\n const remoteFiles = await loadRemoteMiniAppFiles(args.http, workspaceKey, appCode);\n if (!remoteFiles.length) {\n throw new CliError({\n code: CliErrorCode.BUSINESS_RULE_VIOLATION,\n message: `mini app '${appCode}' not found in workspace '${workspaceKey}'`,\n });\n }\n\n const pullFiles = remoteFiles\n .filter((file) => !isDirectoryMarkerPath(file.item.relativePath))\n .map((file) => ({\n localPath: localPathFromRemote(remotePrefix, file.item.relativePath),\n content: file.content.content,\n remote: file.item,\n }));\n await ensureTargetWritable(\n targetDir,\n pullFiles.map((file) => file.localPath).concat([MINI_CONTEXT_FILE]),\n !!args.force,\n );\n await fs.mkdir(targetDir, { recursive: true });\n for (const file of pullFiles) {\n await writeText(path.join(targetDir, file.localPath), file.content);\n }\n\n const context: MiniProjectContext = {\n kind: MINI_CONTEXT_KIND,\n version: MINI_CONTEXT_VERSION,\n appCode,\n workspaceKey,\n remotePrefix,\n tenantId: args.tenantId,\n endpoint: args.endpoint,\n profile: args.profile,\n updatedAt: new Date().toISOString(),\n };\n await writeJson(path.join(targetDir, MINI_CONTEXT_FILE), context);\n\n const localFiles = await readLocalMiniFiles(targetDir);\n const remoteByLocalPath = new Map(\n pullFiles.map((file) => [file.localPath, file.remote] as const),\n );\n await writeMiniState(targetDir, context, localFiles, remoteByLocalPath);\n const manifest = await readPullManifest(targetDir);\n return {\n appCode,\n workspaceKey,\n targetDir,\n files: manifest.files,\n };\n}\n\nexport async function getMiniStatus(rootDir: string): Promise<MiniStatusEntry[]> {\n const root = path.resolve(rootDir);\n const context = await readMiniContext(root);\n const manifest = await readPullManifest(root);\n const localFiles = await readLocalMiniFiles(root);\n return computeStatusEntries(context, manifest, localFiles);\n}\n\nexport async function validateMiniProject(rootDir: string): Promise<MiniValidationResult> {\n const root = path.resolve(rootDir);\n const errors: string[] = [];\n const warnings: string[] = [];\n let context: MiniProjectContext | null = null;\n\n try {\n context = await readMiniContext(root);\n } catch (e) {\n errors.push((e as Error).message);\n }\n\n const manifestPath = path.join(root, 'manifest.json');\n let manifest: Record<string, unknown> | null = null;\n try {\n manifest = JSON.parse(await fs.readFile(manifestPath, 'utf-8')) as Record<string, unknown>;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n errors.push('manifest.json is required');\n } else {\n errors.push(`manifest.json is not valid JSON: ${(e as Error).message}`);\n }\n }\n\n if (context) {\n if (!MINI_APP_CODE_PATTERN.test(context.appCode)) {\n errors.push('appCode must match ^[a-z][a-z0-9-]*$');\n }\n if (context.remotePrefix !== miniRemotePrefix(context.appCode)) {\n errors.push(`context remotePrefix must be ${miniRemotePrefix(context.appCode)}`);\n }\n }\n\n if (manifest) {\n await validateManifestObject(manifest, context, root, errors, warnings);\n }\n\n return {\n valid: errors.length === 0,\n appCode: context?.appCode,\n workspaceKey: context?.workspaceKey,\n errors,\n warnings,\n };\n}\n\nexport async function pushMiniAppProject(args: MiniPushArgs): Promise<MiniPushResult> {\n const root = path.resolve(args.rootDir);\n const validation = await validateMiniProject(root);\n if (!validation.valid) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: `mini project validation failed: ${validation.errors.join('; ')}`,\n });\n }\n\n const context = await readMiniContext(root);\n const manifest = await readPullManifest(root);\n const localFiles = await readLocalMiniFiles(root);\n const remoteFiles = await loadRemoteMiniAppFiles(args.http, context.workspaceKey, context.appCode);\n const plan = buildPushPlan(context, manifest, localFiles, remoteFiles);\n\n const result: MiniPushResult = {\n appCode: context.appCode,\n workspaceKey: context.workspaceKey,\n dryRun: !!args.dryRun,\n operations: plan.operations,\n conflicts: plan.conflicts,\n };\n if (plan.conflicts.length || args.dryRun) {\n return result;\n }\n\n const localByPath = new Map(localFiles.map((file) => [file.localPath, file] as const));\n for (const operation of plan.operations) {\n if (operation.operation === 'skip') continue;\n if (operation.operation === 'delete') {\n if (operation.fileId) await deleteWorkspaceFile(args.http, operation.fileId);\n continue;\n }\n const localFile = localByPath.get(operation.localPath);\n if (!localFile) continue;\n if (operation.operation === 'create') {\n await createWorkspaceFile(\n args.http,\n context.workspaceKey,\n operation.remotePath,\n localFile.content,\n );\n }\n if (operation.operation === 'update' && operation.fileId) {\n await saveWorkspaceFileContent(args.http, operation.fileId, localFile.content);\n }\n }\n\n const latestRemoteFiles = await listWorkspaceFiles(args.http, context.workspaceKey, {\n pageSize: 5000,\n });\n const remoteByLocalPath = new Map<string, WorkspaceFileItem>();\n for (const remote of latestRemoteFiles.data) {\n if (!remote.relativePath.startsWith(context.remotePrefix + '/')) continue;\n remoteByLocalPath.set(localPathFromRemote(context.remotePrefix, remote.relativePath), remote);\n }\n await writeMiniState(root, context, localFiles, remoteByLocalPath);\n return result;\n}\n\nexport async function buildMiniPreviewTargets(\n rootDir: string,\n frontendBaseUrl: string,\n): Promise<MiniPreviewTarget[]> {\n const context = await readMiniContext(path.resolve(rootDir));\n const app = encodeURIComponent(context.appCode);\n const workspace = encodeURIComponent(context.workspaceKey);\n const base = frontendBaseUrl.replace(/\\/+$/, '');\n return [\n {\n key: 'bare',\n name: 'bare runtime',\n url: `${base}/mini.html?app=${app}&workspaceKey=${workspace}`,\n },\n {\n key: 'standalone',\n name: 'standalone page',\n url: `${base}/mini/${app}?workspaceKey=${workspace}`,\n },\n {\n key: 'embedded',\n name: 'embedded page',\n url: `${base}/custom/${app}?workspaceKey=${workspace}`,\n },\n ];\n}\n\nexport interface MiniSmokeFetchResponse {\n status: number;\n}\n\nexport type MiniSmokeFetch = (\n url: string,\n init?: { headers?: Record<string, string>; method: 'GET'; signal?: AbortSignal },\n) => Promise<MiniSmokeFetchResponse>;\n\nexport interface MiniSmokeCheckArgs {\n rootDir: string;\n frontendBaseUrl: string;\n previewTargets: MiniPreviewTarget[];\n fetcher?: MiniSmokeFetch;\n accessToken?: string | null;\n timeoutMs?: number;\n}\n\nexport async function runMiniSmokeChecks(args: MiniSmokeCheckArgs): Promise<MiniSmokeResult> {\n const context = await readMiniContext(path.resolve(args.rootDir));\n const targets = buildMiniSmokeTargets(context, args.frontendBaseUrl, args.previewTargets);\n const results = await Promise.all(\n targets.map((target) =>\n checkMiniSmokeTarget(\n target,\n args.fetcher ?? defaultSmokeFetch,\n args.timeoutMs ?? 5000,\n args.accessToken,\n context.workspaceKey,\n ),\n ),\n );\n const passed = results.filter((target) => target.ok).length;\n const failed = results.length - passed;\n return {\n ok: failed === 0,\n checked: results.length,\n passed,\n failed,\n targets: results,\n };\n}\n\nfunction buildMiniSmokeTargets(\n context: MiniProjectContext,\n frontendBaseUrl: string,\n previewTargets: MiniPreviewTarget[],\n): MiniSmokeTargetResult[] {\n const base = frontendBaseUrl.replace(/\\/+$/, '');\n const app = encodeURIComponent(context.appCode);\n return [\n ...previewTargets.map(\n (target): MiniSmokeTargetResult => ({\n kind: 'preview',\n surface: target.key,\n url: target.url,\n status: null,\n ok: false,\n }),\n ),\n {\n kind: 'manifest',\n url: `${base}/api/mini-apps/${app}`,\n status: null,\n ok: false,\n },\n ...['index.html', 'index.css', 'index.js'].map(\n (file): MiniSmokeTargetResult => ({\n kind: 'asset',\n url: `${base}/static/mini-apps/${app}/${file}`,\n status: null,\n ok: false,\n }),\n ),\n ];\n}\n\nasync function checkMiniSmokeTarget(\n target: MiniSmokeTargetResult,\n fetcher: MiniSmokeFetch,\n timeoutMs: number,\n accessToken?: string | null,\n workspaceKey?: string | null,\n): Promise<MiniSmokeTargetResult> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const headers =\n target.kind !== 'preview'\n ? {\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...(workspaceKey ? { 'X-Workspace-Key': workspaceKey } : {}),\n }\n : undefined;\n const response = await fetcher(target.url, {\n ...(headers ? { headers } : {}),\n method: 'GET',\n signal: controller.signal,\n });\n const ok = response.status >= 200 && response.status < 400;\n return {\n ...target,\n status: response.status,\n ok,\n error: ok ? undefined : `HTTP ${response.status}`,\n };\n } catch (e) {\n return {\n ...target,\n status: null,\n ok: false,\n error: (e as Error).message || 'request failed',\n };\n } finally {\n clearTimeout(timeout);\n }\n}\n\nasync function defaultSmokeFetch(\n url: string,\n init?: { headers?: Record<string, string>; method: 'GET'; signal?: AbortSignal },\n): Promise<MiniSmokeFetchResponse> {\n if (typeof fetch !== 'function') throw new Error('fetch is not available');\n return fetch(url, init);\n}\n\nexport function buildMiniVerifyAgentReport(report: MiniVerifyReport): MiniVerifyAgentReport {\n const statusCounts = countStatuses(report.status);\n const operationCounts = countOperations(report.plan?.operations ?? []);\n const conflicts = report.plan?.conflicts ?? [];\n const dryRunOk = !!report.plan && conflicts.length === 0;\n const smoke = report.smoke ?? null;\n const smokeOk = !smoke || smoke.ok;\n const previewUrls = report.preview.map(\n (target): MiniVerifyPreviewUrl => ({\n surface: target.key,\n name: target.name,\n url: target.url,\n }),\n );\n const safeToPush = report.validate.valid && dryRunOk && previewUrls.length > 0 && smokeOk;\n const nextAction = chooseMiniVerifyNextAction(report, previewUrls, smoke);\n\n return {\n ok: report.ok && smokeOk,\n safeToPush,\n nextAction,\n summary: {\n validate: {\n ok: report.validate.valid,\n errors: report.validate.errors.length,\n warnings: report.validate.warnings.length,\n appCode: report.validate.appCode,\n workspaceKey: report.validate.workspaceKey,\n },\n status: {\n total: report.status.length,\n changed: statusCounts.added + statusCounts.modified + statusCounts.deleted,\n added: statusCounts.added,\n modified: statusCounts.modified,\n deleted: statusCounts.deleted,\n unchanged: statusCounts.unchanged,\n },\n dryRun: {\n ok: dryRunOk,\n skipped: !report.plan,\n skippedReason: report.plan ? undefined : report.planSkipped,\n operations: operationCounts.create + operationCounts.update + operationCounts.delete,\n creates: operationCounts.create,\n updates: operationCounts.update,\n deletes: operationCounts.delete,\n skips: operationCounts.skip,\n conflicts: conflicts.length,\n },\n preview: {\n ok: previewUrls.length > 0,\n count: previewUrls.length,\n surfaces: previewUrls.map((target) => target.surface),\n },\n },\n changedFiles: report.status\n .filter((entry) => entry.status !== 'unchanged')\n .map(\n (entry): MiniVerifyChangedFile => ({\n path: entry.localPath,\n status: entry.status,\n remotePath: entry.remotePath,\n }),\n ),\n conflicts,\n previewUrls,\n browserAcceptance: buildMiniVerifyBrowserAcceptance(report, previewUrls),\n smoke,\n };\n}\n\nfunction buildMiniVerifyBrowserAcceptance(\n report: MiniVerifyReport,\n previewUrls: MiniVerifyPreviewUrl[],\n): MiniVerifyBrowserAcceptance {\n const recommendedUrl = preferredBrowserPreview(previewUrls)?.url;\n const appCode = report.validate.appCode;\n return {\n recommendedUrl,\n expectedTexts: appCode ? [appCode] : [],\n expectedNetwork: expectedBrowserNetwork(report, recommendedUrl),\n console: {\n failOn: ['error', 'warn'],\n },\n };\n}\n\nfunction preferredBrowserPreview(\n previewUrls: MiniVerifyPreviewUrl[],\n): MiniVerifyPreviewUrl | undefined {\n return (\n previewUrls.find((target) => target.surface === 'standalone') ??\n previewUrls.find((target) => target.surface === 'bare') ??\n previewUrls[0]\n );\n}\n\nfunction expectedBrowserNetwork(report: MiniVerifyReport, recommendedUrl?: string): string[] {\n const smokeUrls = report.smoke?.targets\n .filter((target) => target.kind !== 'preview')\n .map((target) => target.url);\n if (smokeUrls?.length) return Array.from(new Set(smokeUrls));\n\n const appCode = report.validate.appCode;\n if (!recommendedUrl || !appCode) return [];\n try {\n const origin = new URL(recommendedUrl).origin;\n return [\n `${origin}/api/mini-apps/${appCode}`,\n `${origin}/static/mini-apps/${appCode}/index.html`,\n `${origin}/static/mini-apps/${appCode}/index.css`,\n `${origin}/static/mini-apps/${appCode}/index.js`,\n ];\n } catch {\n return [];\n }\n}\n\nfunction chooseMiniVerifyNextAction(\n report: MiniVerifyReport,\n previewUrls: MiniVerifyPreviewUrl[],\n smoke: MiniSmokeResult | null,\n): MiniVerifyNextAction {\n if (smoke && !smoke.ok) return 'inspect-preview';\n if (!report.validate.valid) return 'fix-validation';\n if (!report.plan) return 'inspect-status';\n if (report.plan.conflicts.length) return 'resolve-conflict';\n if (!previewUrls.length) return 'inspect-preview';\n return 'push';\n}\n\nexport function formatMiniVerifyReport(report: MiniVerifyReport): string {\n const totalSteps = report.smoke ? 5 : 4;\n const lines: string[] = [];\n lines.push(`verify: ${report.ok ? 'OK' : 'FAIL'}`);\n lines.push('');\n\n // 1. validate\n lines.push(`[1/${totalSteps}] validate: ${report.validate.valid ? '✓ pass' : '✗ fail'}`);\n if (report.validate.errors.length) {\n for (const err of report.validate.errors) lines.push(` error: ${err}`);\n }\n if (report.validate.warnings.length) {\n for (const warn of report.validate.warnings) lines.push(` warn: ${warn}`);\n }\n\n // 2. status\n const statusBucket = { added: 0, modified: 0, deleted: 0, unchanged: 0 };\n for (const row of report.status) statusBucket[row.status] += 1;\n lines.push(\n `[2/${totalSteps}] status: ${statusBucket.added} added, ${statusBucket.modified} modified, ${statusBucket.deleted} deleted, ${statusBucket.unchanged} unchanged`,\n );\n for (const row of report.status) {\n if (row.status === 'unchanged') continue;\n lines.push(` ${row.status.padEnd(9)} ${row.localPath}`);\n }\n\n // 3. push dry-run\n if (report.plan) {\n if (report.plan.conflicts.length) {\n lines.push(`[3/${totalSteps}] dry-run: ✗ ${report.plan.conflicts.length} conflict(s)`);\n for (const c of report.plan.conflicts) lines.push(` ${c.localPath}: ${c.reason}`);\n } else {\n const opCount = report.plan.operations.filter((op) => op.operation !== 'skip').length;\n lines.push(`[3/${totalSteps}] dry-run: ✓ ${opCount} operation(s) planned (no remote writes)`);\n for (const op of report.plan.operations) {\n if (op.operation === 'skip') continue;\n lines.push(` ${op.operation.padEnd(7)} ${op.localPath}`);\n }\n }\n } else {\n lines.push(`[3/${totalSteps}] dry-run: - skipped (${report.planSkipped ?? 'unknown reason'})`);\n }\n\n // 4. preview URLs\n if (report.preview.length) {\n lines.push(`[4/${totalSteps}] preview:`);\n for (const target of report.preview) lines.push(` ${target.key.padEnd(11)} ${target.url}`);\n } else {\n lines.push(`[4/${totalSteps}] preview: - (no targets)`);\n }\n\n // 5. smoke HTTP checks\n if (report.smoke) {\n const marker = report.smoke.ok ? '✓' : '✗';\n lines.push(\n `[5/5] smoke: ${marker} ${report.smoke.passed}/${report.smoke.checked} passed`,\n );\n for (const target of report.smoke.targets) {\n const targetMarker = target.ok ? '✓' : '✗';\n const label = target.surface ? `${target.kind}:${target.surface}` : target.kind;\n const status = target.status ?? '-';\n const error = target.error ? ` (${target.error})` : '';\n lines.push(` ${targetMarker} ${label.padEnd(18)} ${status} ${target.url}${error}`);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction countStatuses(status: MiniStatusEntry[]): Record<MiniStatusEntry['status'], number> {\n const counts: Record<MiniStatusEntry['status'], number> = {\n added: 0,\n modified: 0,\n deleted: 0,\n unchanged: 0,\n };\n for (const entry of status) counts[entry.status] += 1;\n return counts;\n}\n\nfunction countOperations(operations: MiniPushPlanItem[]): Record<MiniPushOperation, number> {\n const counts: Record<MiniPushOperation, number> = {\n create: 0,\n update: 0,\n delete: 0,\n skip: 0,\n };\n for (const operation of operations) counts[operation.operation] += 1;\n return counts;\n}\n\nexport async function readMiniContext(rootDir: string): Promise<MiniProjectContext> {\n const file = path.join(rootDir, MINI_CONTEXT_FILE);\n let parsed: unknown;\n try {\n parsed = JSON.parse(await fs.readFile(file, 'utf-8'));\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new CliError({\n code: CliErrorCode.CONFIG_PARSE_ERROR,\n message: `${MINI_CONTEXT_FILE} not found; run hcm workspace add mini <appCode> or hcm workspace pull first`,\n });\n }\n throw new CliError({\n code: CliErrorCode.CONFIG_PARSE_ERROR,\n message: `${MINI_CONTEXT_FILE} is invalid: ${(e as Error).message}`,\n });\n }\n const obj = asRecord(parsed);\n if (obj.kind !== MINI_CONTEXT_KIND || obj.version !== MINI_CONTEXT_VERSION) {\n throw new CliError({\n code: CliErrorCode.CONFIG_PARSE_ERROR,\n message: `${MINI_CONTEXT_FILE} is not an HCM mini app context`,\n });\n }\n return {\n kind: MINI_CONTEXT_KIND,\n version: MINI_CONTEXT_VERSION,\n appCode: normalizeAppCode(stringRequired(obj.appCode, 'appCode')),\n workspaceKey: normalizeWorkspaceKey(stringRequired(obj.workspaceKey, 'workspaceKey')),\n remotePrefix: stringRequired(obj.remotePrefix, 'remotePrefix'),\n tenantId: stringOptional(obj.tenantId),\n endpoint: stringOptional(obj.endpoint),\n profile: stringOptional(obj.profile),\n updatedAt: stringOptional(obj.updatedAt) ?? new Date(0).toISOString(),\n };\n}\n\nexport async function readPullManifest(rootDir: string): Promise<MiniPullManifest> {\n const file = path.join(rootDir, MINI_STATE_DIR, MINI_PULL_MANIFEST_FILE);\n try {\n const parsed = JSON.parse(await fs.readFile(file, 'utf-8')) as MiniPullManifest;\n return {\n version: MINI_CONTEXT_VERSION,\n appCode: parsed.appCode ? normalizeAppCode(parsed.appCode) : '',\n workspaceKey: parsed.workspaceKey ? normalizeWorkspaceKey(parsed.workspaceKey) : '',\n remotePrefix: parsed.remotePrefix ?? '',\n profile: parsed.profile,\n pulledAt: parsed.pulledAt,\n files: Array.isArray(parsed.files) ? parsed.files : [],\n };\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n return {\n version: MINI_CONTEXT_VERSION,\n appCode: '',\n workspaceKey: '',\n remotePrefix: '',\n pulledAt: new Date(0).toISOString(),\n files: [],\n };\n }\n throw e;\n }\n}\n\nasync function validateManifestObject(\n manifest: Record<string, unknown>,\n context: MiniProjectContext | null,\n root: string,\n errors: string[],\n warnings: string[],\n): Promise<void> {\n for (const key of Object.keys(manifest)) {\n if (!TOP_LEVEL_MANIFEST_KEYS.has(key)) errors.push(`manifest has unsupported key: ${key}`);\n }\n requireString(manifest, 'schemaVersion', errors, (value) => value === '1.0');\n requireString(manifest, 'code', errors, (value) => /^[a-zA-Z0-9-]+$/.test(value));\n requireString(manifest, 'name', errors, (value) => value.trim().length > 0);\n requireString(manifest, 'version', errors, (value) => SEMVER_PATTERN.test(value));\n requireString(manifest, 'sdkVersion', errors, (value) => SEMVER_PATTERN.test(value));\n\n const code = typeof manifest.code === 'string' ? manifest.code : '';\n if (context && code && code !== context.appCode) {\n errors.push(`manifest code must equal context appCode: ${context.appCode}`);\n }\n const requiredScopes = asStringArray(manifest.requiredScopes);\n if (!requiredScopes.length) {\n errors.push('manifest requiredScopes must contain at least one scope');\n }\n for (const scope of requiredScopes) {\n if (!SCOPE_PATTERN.test(scope)) {\n errors.push(`manifest requiredScopes has invalid scope: ${scope}`);\n }\n }\n\n const surfaces = asStringArray(manifest.surfaces);\n const defaultSurface = typeof manifest.defaultSurface === 'string' ? manifest.defaultSurface : '';\n if (surfaces.length) {\n for (const surface of surfaces) {\n if (surface !== 'embedded' && surface !== 'standalone') {\n errors.push(`manifest surfaces has invalid value: ${surface}`);\n }\n }\n if (new Set(surfaces).size !== surfaces.length) {\n errors.push('manifest surfaces must be unique');\n }\n } else {\n warnings.push('manifest surfaces is omitted; backend default may apply');\n }\n if (defaultSurface && surfaces.length && !surfaces.includes(defaultSurface)) {\n errors.push('manifest defaultSurface must be declared in surfaces');\n }\n\n const entry = asRecord(manifest.entry);\n if (!Object.keys(entry).length) {\n errors.push('manifest entry is required');\n }\n for (const key of Object.keys(entry)) {\n if (!ENTRY_KEYS.has(key)) errors.push(`manifest entry has unsupported key: ${key}`);\n }\n const html = stringOptional(entry.html);\n const js = stringOptional(entry.js);\n const css = stringOptional(entry.css);\n if (!html && !js) {\n errors.push('manifest entry must define html or js');\n }\n await validateEntryPath(root, 'entry.html', html, '.html', errors);\n await validateEntryPath(root, 'entry.js', js, '.js', errors);\n await validateEntryPath(root, 'entry.css', css, '.css', errors);\n await validateEntryRequiredScopes(root, js, requiredScopes, warnings);\n\n const bundleObjectRef = asRecord(manifest.bundleObjectRef);\n const pathSpec = asRecord(bundleObjectRef.pathSpec);\n const pathSegment = stringOptional(pathSpec.pathSegment);\n if (context && pathSegment && pathSegment !== miniRemotePrefix(context.appCode)) {\n errors.push(\n `manifest bundleObjectRef.pathSpec.pathSegment must be ${miniRemotePrefix(context.appCode)}`,\n );\n }\n}\n\nasync function validateEntryPath(\n root: string,\n field: string,\n value: string | undefined,\n suffix: string,\n errors: string[],\n): Promise<void> {\n if (!value) return;\n if (!value.endsWith(suffix)) errors.push(`${field} must end with ${suffix}`);\n try {\n normalizeRelativePath(value);\n } catch (e) {\n errors.push(`${field} is invalid: ${(e as Error).message}`);\n return;\n }\n try {\n await fs.access(path.join(root, value));\n } catch {\n errors.push(`${field} target file does not exist: ${value}`);\n }\n}\n\nasync function validateEntryRequiredScopes(\n root: string,\n jsPath: string | undefined,\n requiredScopes: string[],\n warnings: string[],\n): Promise<void> {\n if (!jsPath) return;\n const absolutePath = path.resolve(root, jsPath);\n if (!absolutePath.startsWith(root + path.sep)) return;\n let source = '';\n try {\n source = await fs.readFile(absolutePath, 'utf-8');\n } catch {\n return;\n }\n\n const declaredScopes = new Set(requiredScopes);\n for (const scope of inferMiniSdkRequiredScopes(source)) {\n if (!declaredScopes.has(scope)) {\n warnings.push(`manifest requiredScopes may be missing ${scope} for ${jsPath} SDK usage`);\n }\n }\n}\n\nfunction inferMiniSdkRequiredScopes(source: string): string[] {\n const constants = readMiniModelConstants(source);\n const scopes = new Set<string>();\n let match: RegExpExecArray | null;\n\n MINI_SDK_MODEL_CALL_PATTERN.lastIndex = 0;\n while ((match = MINI_SDK_MODEL_CALL_PATTERN.exec(source))) {\n const method = match[1];\n if (!method) continue;\n const model = match[2] ?? constants.get(match[3] ?? '');\n const action = MINI_SDK_SCOPE_METHODS[method];\n if (model && action) scopes.add(`${model}.${action}`);\n }\n\n MINI_SDK_ACTION_CALL_PATTERN.lastIndex = 0;\n while ((match = MINI_SDK_ACTION_CALL_PATTERN.exec(source))) {\n const model = match[1] ?? constants.get(match[2] ?? '');\n const action = match[3];\n if (model && action) scopes.add(`${model}.${action}`);\n }\n\n return Array.from(scopes);\n}\n\nfunction readMiniModelConstants(source: string): Map<string, string> {\n const constants = new Map<string, string>();\n let match: RegExpExecArray | null;\n MINI_MODEL_CONST_PATTERN.lastIndex = 0;\n while ((match = MINI_MODEL_CONST_PATTERN.exec(source))) {\n if (!match[1] || !match[2]) continue;\n constants.set(match[1], match[2]);\n }\n return constants;\n}\n\nfunction buildPushPlan(\n context: MiniProjectContext,\n manifest: MiniPullManifest,\n localFiles: MiniLocalFile[],\n remoteFiles: Array<{ item: WorkspaceFileItem; content: WorkspaceFileContent }>,\n): { operations: MiniPushPlanItem[]; conflicts: MiniPushConflict[] } {\n const status = computeStatusEntries(context, manifest, localFiles);\n const baseByLocal = new Map(manifest.files.map((file) => [file.localPath, file] as const));\n const remoteByLocal = new Map(\n remoteFiles.map((file) => [\n localPathFromRemote(context.remotePrefix, file.item.relativePath),\n file,\n ] as const),\n );\n const operations: MiniPushPlanItem[] = [];\n const conflicts: MiniPushConflict[] = [];\n\n for (const entry of status) {\n const remote = remoteByLocal.get(entry.localPath);\n const base = baseByLocal.get(entry.localPath);\n const remoteSha = remote ? sha256(remote.content.content) : undefined;\n if (entry.status === 'added') {\n if (remote) {\n conflicts.push(conflict(entry, 'remote file already exists but was not in local snapshot'));\n } else {\n operations.push({ operation: 'create', localPath: entry.localPath, remotePath: entry.remotePath });\n }\n continue;\n }\n if (entry.status === 'modified') {\n if (!remote) {\n conflicts.push(conflict(entry, 'remote file was deleted after last pull'));\n } else if (base && remoteSha !== base.sha256) {\n conflicts.push(conflict(entry, 'remote file changed after last pull'));\n } else {\n operations.push({\n operation: 'update',\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n fileId: remote.item.id,\n });\n }\n continue;\n }\n if (entry.status === 'deleted') {\n if (!remote) {\n operations.push({\n operation: 'skip',\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n reason: 'already deleted remotely',\n });\n } else if (base && remoteSha !== base.sha256) {\n conflicts.push(conflict(entry, 'remote file changed after last pull'));\n } else {\n operations.push({\n operation: 'delete',\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n fileId: remote.item.id,\n });\n }\n continue;\n }\n operations.push({\n operation: 'skip',\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n fileId: remote?.item.id,\n reason: 'unchanged',\n });\n }\n return { operations, conflicts };\n}\n\nfunction computeStatusEntries(\n context: MiniProjectContext,\n manifest: MiniPullManifest,\n localFiles: MiniLocalFile[],\n): MiniStatusEntry[] {\n const baseByLocal = new Map(manifest.files.map((file) => [file.localPath, file] as const));\n const localByPath = new Map(localFiles.map((file) => [file.localPath, file] as const));\n const paths = [...new Set([...baseByLocal.keys(), ...localByPath.keys()])].sort();\n return paths.map((localPath) => {\n const base = baseByLocal.get(localPath);\n const local = localByPath.get(localPath);\n const remotePath = base?.remotePath ?? remotePathFromLocal(context.appCode, localPath);\n if (!base && local) {\n return { localPath, remotePath, status: 'added', sha256: local.sha256 };\n }\n if (base && !local) {\n return {\n localPath,\n remotePath,\n status: 'deleted',\n fileId: base.fileId,\n baseSha256: base.sha256,\n };\n }\n if (base && local && base.sha256 !== local.sha256) {\n return {\n localPath,\n remotePath,\n status: 'modified',\n fileId: base.fileId,\n sha256: local.sha256,\n baseSha256: base.sha256,\n };\n }\n return {\n localPath,\n remotePath,\n status: 'unchanged',\n fileId: base?.fileId,\n sha256: local?.sha256,\n baseSha256: base?.sha256,\n };\n });\n}\n\nasync function loadRemoteMiniAppFiles(\n http: AxiosInstance,\n workspaceKey: string,\n appCode: string,\n): Promise<Array<{ item: WorkspaceFileItem; content: WorkspaceFileContent }>> {\n const prefix = miniRemotePrefix(appCode) + '/';\n const list = await listWorkspaceFiles(http, workspaceKey, { pageSize: 5000 });\n const result: Array<{ item: WorkspaceFileItem; content: WorkspaceFileContent }> = [];\n for (const item of list.data) {\n if (!item.relativePath.startsWith(prefix) || isDirectoryMarkerPath(item.relativePath)) continue;\n const content = await loadWorkspaceFileContent(http, item.id);\n result.push({ item, content });\n }\n return result;\n}\n\nasync function readLocalMiniFiles(root: string): Promise<MiniLocalFile[]> {\n const files: MiniLocalFile[] = [];\n await walk(root, '', files);\n files.sort((a, b) => a.localPath.localeCompare(b.localPath));\n return files;\n}\n\nasync function walk(root: string, relativeDir: string, files: MiniLocalFile[]): Promise<void> {\n const absoluteDir = path.join(root, relativeDir);\n let entries: Dirent[];\n try {\n entries = await fs.readdir(absoluteDir, { withFileTypes: true });\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw e;\n }\n for (const entry of entries) {\n if (entry.name === MINI_CONTEXT_FILE) continue;\n if (entry.isDirectory() && EXCLUDED_DIRS.has(entry.name)) continue;\n const localPath = normalizeRelativePath(path.posix.join(toPosix(relativeDir), entry.name));\n const absolutePath = path.join(root, localPath);\n if (entry.isDirectory()) {\n await walk(root, localPath, files);\n } else if (entry.isFile()) {\n const content = await fs.readFile(absolutePath, 'utf-8');\n files.push({\n localPath,\n absolutePath,\n content,\n sha256: sha256(content),\n size: Buffer.byteLength(content, 'utf-8'),\n });\n }\n }\n}\n\nasync function writeMiniState(\n root: string,\n context: MiniProjectContext,\n files: MiniLocalFile[],\n remoteByLocalPath: Map<string, WorkspaceFileItem>,\n): Promise<void> {\n const stateDir = path.join(root, MINI_STATE_DIR);\n const baseDir = path.join(stateDir, 'base');\n await fs.rm(baseDir, { recursive: true, force: true });\n await fs.mkdir(baseDir, { recursive: true });\n const snapshots: MiniFileSnapshot[] = [];\n for (const file of files) {\n await writeText(path.join(baseDir, file.localPath), file.content);\n snapshots.push(toSnapshot(context, file, remoteByLocalPath.get(file.localPath)));\n }\n await writeJson(path.join(stateDir, MINI_PULL_MANIFEST_FILE), {\n version: MINI_CONTEXT_VERSION,\n appCode: context.appCode,\n workspaceKey: context.workspaceKey,\n remotePrefix: context.remotePrefix,\n profile: context.profile,\n pulledAt: new Date().toISOString(),\n files: snapshots,\n } satisfies MiniPullManifest);\n}\n\nfunction toSnapshot(\n context: MiniProjectContext,\n file: MiniLocalFile,\n remote?: WorkspaceFileItem,\n): MiniFileSnapshot {\n return {\n localPath: file.localPath,\n remotePath: remote?.relativePath ?? remotePathFromLocal(context.appCode, file.localPath),\n fileId: remote?.id,\n sha256: file.sha256,\n size: file.size,\n lastModified: remote?.lastModified,\n };\n}\n\nfunction buildMiniAppTemplateFiles(args: {\n appCode: string;\n name?: string;\n defaultSurface: MiniSurface;\n templateKind?: MiniTemplateKind;\n writableModel?: string;\n writableFields?: string[] | null;\n writableDefaults?: Record<string, unknown>;\n writableTodayDefaultFields?: string[];\n requiredScopes: string[];\n tenantId: string;\n}): Array<{ localPath: string; content: string }> {\n return buildMiniAppTemplateFileSet(args);\n}\n\nasync function ensureTargetWritable(\n targetDir: string,\n relativePaths: string[],\n force: boolean,\n): Promise<void> {\n if (force) return;\n const existing: string[] = [];\n for (const relativePath of relativePaths) {\n try {\n await fs.access(path.join(targetDir, relativePath));\n existing.push(relativePath);\n } catch {\n /* file does not exist */\n }\n }\n if (existing.length) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: `target contains existing files: ${existing.join(', ')} (use --force to overwrite)`,\n });\n }\n}\n\nfunction validateRequiredPath(value: string, fieldName: string): string {\n if (!value || !value.trim()) {\n throw new CliError({ code: CliErrorCode.MISSING_FLAG, message: `${fieldName} is required` });\n }\n return value.trim();\n}\n\nfunction normalizeAppCode(value: string): string {\n const normalized = validateRequiredPath(value, 'appCode');\n if (!MINI_APP_CODE_PATTERN.test(normalized)) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: 'appCode must match ^[a-z][a-z0-9-]*$',\n });\n }\n return normalized;\n}\n\nfunction normalizeWorkspaceKey(value: string): string {\n const normalized = validateRequiredPath(value, 'workspaceKey');\n if (!/^[A-Za-z0-9._-]+$/.test(normalized)) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: 'workspaceKey only supports letters, numbers, dot, underscore and dash',\n });\n }\n return normalized;\n}\n\nfunction normalizeRelativePath(value: string): string {\n const normalized = toPosix(value).replace(/^\\/+/, '').replace(/\\/+/g, '/');\n if (!normalized || normalized === '.' || normalized.includes('\\0')) {\n throw new Error('path is empty');\n }\n for (const segment of normalized.split('/')) {\n if (!segment || segment === '.' || segment === '..') {\n throw new Error(`unsafe path segment: ${segment}`);\n }\n }\n return normalized;\n}\n\nfunction miniRemotePrefix(appCode: string): string {\n return `${MINI_REMOTE_ROOT}/${normalizeAppCode(appCode)}`;\n}\n\nfunction remotePathFromLocal(appCode: string, localPath: string): string {\n return `${miniRemotePrefix(appCode)}/${normalizeRelativePath(localPath)}`;\n}\n\nfunction localPathFromRemote(remotePrefix: string, remotePath: string): string {\n const prefix = remotePrefix + '/';\n if (!remotePath.startsWith(prefix)) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: `remote path is outside mini app prefix: ${remotePath}`,\n });\n }\n return normalizeRelativePath(remotePath.slice(prefix.length));\n}\n\nfunction isDirectoryMarkerPath(relativePath: string): boolean {\n return relativePath.endsWith('/.keep') || relativePath === '.keep';\n}\n\nfunction sha256(content: string): string {\n return crypto.createHash('sha256').update(content, 'utf-8').digest('hex');\n}\n\nasync function writeText(file: string, content: string): Promise<void> {\n await fs.mkdir(path.dirname(file), { recursive: true });\n await fs.writeFile(file, content, 'utf-8');\n}\n\nasync function writeJson(file: string, value: unknown): Promise<void> {\n await writeText(file, JSON.stringify(value, null, 2) + '\\n');\n}\n\nfunction conflict(entry: MiniStatusEntry, reason: string): MiniPushConflict {\n return {\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n reason,\n };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};\n}\n\nfunction asStringArray(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];\n}\n\nfunction requireString(\n obj: Record<string, unknown>,\n key: string,\n errors: string[],\n predicate?: (value: string) => boolean,\n): void {\n const value = obj[key];\n if (typeof value !== 'string' || !value.trim()) {\n errors.push(`manifest ${key} is required`);\n return;\n }\n if (predicate && !predicate(value)) {\n errors.push(`manifest ${key} is invalid`);\n }\n}\n\nfunction stringRequired(value: unknown, fieldName: string): string {\n if (typeof value !== 'string' || !value.trim()) {\n throw new CliError({\n code: CliErrorCode.CONFIG_PARSE_ERROR,\n message: `${MINI_CONTEXT_FILE} missing ${fieldName}`,\n });\n }\n return value;\n}\n\nfunction stringOptional(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined;\n}\n\nfunction toPosix(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n","import type { FieldMeta, ModelDescription } from '../meta/describe';\nimport type { MiniSurface, MiniTemplateKind } from './types';\nimport { SDK_VERSION } from '../package-version';\n\nexport interface MiniTemplateArgs {\n appCode: string;\n name?: string;\n defaultSurface: MiniSurface;\n requiredScopes: string[];\n tenantId: string;\n templateKind?: MiniTemplateKind;\n writableModel?: string;\n writableFields?: string[] | null;\n writableDefaults?: Record<string, unknown>;\n writableTodayDefaultFields?: string[];\n}\n\nexport interface MiniWritableTemplateConfig {\n model?: string;\n fields?: string[] | null;\n defaults?: Record<string, unknown>;\n todayDefaultFields?: string[];\n}\n\ninterface MiniReadTemplateConfig {\n model: string;\n modelLabel: string;\n}\n\nconst DEFAULT_MULTI_PAGE_MODEL = 'Employee';\nconst DEFAULT_WRITABLE_MODEL = 'OrgUnit';\n// OrgUnit 的编号字段是 `code`(CLAUDE.md 字段命名规范:编号/编码一律 code)。\n// 后端 OrgUnit.yml 里 unitCode 仅作 AI synonym 存在,不是可写字段——\n// 曾误用 unitCode 导致 writable-workbench 模板生成绑定到不存在字段的表单。\nconst DEFAULT_WRITABLE_FIELDS = ['code', 'name', 'unitType', 'status', 'effecDate', 'expireDate'];\nconst DEFAULT_WRITABLE_DEFAULTS: Record<string, unknown> = {\n unitType: 'DEPT',\n status: 'ACTIVE',\n expireDate: '9999-12-31',\n};\nconst DEFAULT_WRITABLE_TODAY_FIELDS = ['effecDate'];\nconst SYSTEM_FIELD_NAMES = new Set([\n 'id',\n 'tenantId',\n 'createdAt',\n 'createdBy',\n 'updatedAt',\n 'updatedBy',\n 'deletedAt',\n 'deletedBy',\n 'version',\n]);\nconst PREFERRED_WRITABLE_FIELDS = [\n 'code',\n 'name',\n 'unitType',\n 'status',\n 'effecDate',\n 'effectiveDate',\n 'startDate',\n 'expireDate',\n 'endDate',\n 'description',\n 'remark',\n 'notes',\n];\nconst MAX_AUTO_WRITABLE_FIELDS = 10;\nconst REFERENCE_FIELD_MODELS: Record<string, string> = {\n employeeId: 'Employee',\n managerId: 'Employee',\n orgUnitId: 'OrgUnit',\n organizationId: 'OrgUnit',\n positionId: 'Position',\n reportToId: 'Position',\n jobId: 'Job',\n jobGradeId: 'JobGrade',\n jobFamilyId: 'JobFamily',\n};\n\nexport function defaultMiniRequiredScopes(\n templateKind: MiniTemplateKind = 'dashboard',\n writableConfig: MiniWritableTemplateConfig = {},\n): string[] {\n if (templateKind === 'writable-workbench') {\n const model = normalizeWritableModel(writableConfig.model);\n return unique([\n `${model}.list`,\n `${model}.get`,\n `${model}.create`,\n `${model}.update`,\n `${model}.delete`,\n ...referenceScopesForWritableFields(writableConfig.fields),\n ]);\n }\n if (templateKind === 'multi-page') {\n const model = normalizeReadModel(writableConfig.model);\n return [`${model}.list`, `${model}.get`];\n }\n return ['Employee.list'];\n}\n\nexport function buildMiniAppTemplateFiles(args: MiniTemplateArgs): Array<{ localPath: string; content: string }> {\n const templateKind = args.templateKind ?? 'dashboard';\n const displayName = args.name?.trim() || args.appCode;\n const writableConfig = buildWritableTemplateConfig(args);\n const readConfig = buildReadTemplateConfig(args);\n const manifest = {\n schemaVersion: '1.0',\n code: args.appCode,\n name: displayName,\n version: SDK_VERSION,\n sdkVersion: SDK_VERSION,\n surfaces: ['embedded', 'standalone'],\n defaultSurface: args.defaultSurface,\n entry: { html: 'index.html', css: 'index.css', js: 'index.js' },\n bundleObjectRef: {\n tenantId: args.tenantId,\n storageType: 'LOCAL',\n pathSpec: { pathSegment: `mini_apps/${args.appCode}`, fileName: '' },\n },\n requiredScopes: args.requiredScopes,\n };\n\n return [\n { localPath: 'manifest.json', content: JSON.stringify(manifest, null, 2) + '\\n' },\n {\n localPath: 'index.html',\n content: templateHtml(templateKind, args.appCode, displayName, writableConfig),\n },\n {\n localPath: 'index.css',\n content: templateCss(templateKind),\n },\n {\n localPath: 'index.js',\n content: templateJs(templateKind, args.appCode, displayName, writableConfig, readConfig),\n },\n ];\n}\n\nfunction templateHtml(\n templateKind: MiniTemplateKind,\n appCode: string,\n displayName: string,\n writableConfig: ReturnType<typeof buildWritableTemplateConfig>,\n): string {\n if (templateKind === 'writable-workbench') return writableHtml(appCode, displayName, writableConfig);\n if (templateKind === 'multi-page') return multiPageHtml(appCode, displayName);\n return dashboardHtml(appCode, displayName);\n}\n\nfunction templateCss(templateKind: MiniTemplateKind): string {\n if (templateKind === 'writable-workbench') return writableCss();\n if (templateKind === 'multi-page') return multiPageCss();\n return dashboardCss();\n}\n\nfunction templateJs(\n templateKind: MiniTemplateKind,\n appCode: string,\n displayName: string,\n writableConfig: ReturnType<typeof buildWritableTemplateConfig>,\n readConfig: MiniReadTemplateConfig,\n): string {\n if (templateKind === 'writable-workbench') return writableJs(writableConfig);\n if (templateKind === 'multi-page') return multiPageJs(appCode, displayName, readConfig);\n return dashboardJs();\n}\n\nfunction buildReadTemplateConfig(args: MiniTemplateArgs): MiniReadTemplateConfig {\n const model = normalizeReadModel(args.writableModel);\n return {\n model,\n modelLabel: modelDisplayLabel(model),\n };\n}\n\nfunction buildWritableTemplateConfig(args: MiniTemplateArgs): Required<MiniWritableTemplateConfig> & { modelLabel: string } {\n const model = normalizeWritableModel(args.writableModel);\n const fields =\n args.writableFields === undefined\n ? model === DEFAULT_WRITABLE_MODEL\n ? [...DEFAULT_WRITABLE_FIELDS]\n : null\n : args.writableFields;\n const defaults =\n args.writableDefaults !== undefined\n ? args.writableDefaults\n : model === DEFAULT_WRITABLE_MODEL\n ? { ...DEFAULT_WRITABLE_DEFAULTS }\n : {};\n const todayDefaultFields =\n args.writableTodayDefaultFields !== undefined\n ? args.writableTodayDefaultFields\n : model === DEFAULT_WRITABLE_MODEL\n ? [...DEFAULT_WRITABLE_TODAY_FIELDS]\n : [];\n return {\n model,\n fields,\n defaults,\n todayDefaultFields,\n modelLabel: modelDisplayLabel(model),\n };\n}\n\nexport function inferMiniWritableTemplateOptionsFromModel(\n description: ModelDescription,\n): { fields: string[]; defaults: Record<string, unknown>; todayDefaultFields: string[] } {\n const writableFields = inferWritableFields(description.fields);\n const writableFieldSet = new Set(writableFields);\n return {\n fields: writableFields,\n defaults: inferWritableDefaults(description.fields, writableFieldSet),\n todayDefaultFields: inferTodayDefaultFields(description.fields, writableFieldSet),\n };\n}\n\nfunction inferWritableFields(fields: FieldMeta[]): string[] {\n const editable = fields.map((field) => field.name).filter((name) => name && !SYSTEM_FIELD_NAMES.has(name));\n const editableSet = new Set(editable);\n const required = fields\n .filter((field) => field.required === true && editableSet.has(field.name))\n .map((field) => field.name);\n const preferred = PREFERRED_WRITABLE_FIELDS.filter((name) => editableSet.has(name));\n const inferred = unique([...required, ...preferred]);\n return (inferred.length ? inferred : editable).slice(0, MAX_AUTO_WRITABLE_FIELDS);\n}\n\nfunction inferWritableDefaults(fields: FieldMeta[], writableFieldSet: Set<string>): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n if (hasWritableField(fields, writableFieldSet, 'unitType')) defaults.unitType = 'DEPT';\n if (hasWritableField(fields, writableFieldSet, 'status')) defaults.status = 'ACTIVE';\n if (hasWritableField(fields, writableFieldSet, 'expireDate')) defaults.expireDate = '9999-12-31';\n if (hasWritableField(fields, writableFieldSet, 'endDate')) defaults.endDate = '9999-12-31';\n return defaults;\n}\n\nfunction inferTodayDefaultFields(fields: FieldMeta[], writableFieldSet: Set<string>): string[] {\n return ['effecDate', 'effectiveDate', 'startDate'].filter((name) => hasWritableField(fields, writableFieldSet, name));\n}\n\nfunction hasWritableField(fields: FieldMeta[], writableFieldSet: Set<string>, name: string): boolean {\n return writableFieldSet.has(name) && fields.some((field) => field.name === name);\n}\n\nfunction unique(values: string[]): string[] {\n return Array.from(new Set(values));\n}\n\nfunction referenceScopesForWritableFields(fields?: string[] | null): string[] {\n if (!fields?.length) return [];\n return unique(\n fields.flatMap((field) => {\n const model = REFERENCE_FIELD_MODELS[field];\n return model ? [`${model}.list`, `${model}.get`] : [];\n }),\n );\n}\n\nfunction normalizeWritableModel(model?: string): string {\n return model?.trim() || DEFAULT_WRITABLE_MODEL;\n}\n\nfunction normalizeReadModel(model?: string): string {\n return model?.trim() || DEFAULT_MULTI_PAGE_MODEL;\n}\n\nfunction modelDisplayLabel(model: string): string {\n const labels: Record<string, string> = {\n Employee: '员工',\n OrgUnit: '组织单元',\n Position: '岗位',\n };\n return labels[model] ?? `${model} 记录`;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n}\n\nfunction dashboardHtml(appCode: string, displayName: string): string {\n return `<section class=\"hcm-mini-app\" data-mini-app=\"${appCode}\">\n <header class=\"hcm-mini-app__header\">\n <p class=\"hcm-mini-app__eyebrow\">HCM Mini App</p>\n <h1>${displayName}</h1>\n </header>\n <main class=\"hcm-mini-app__body\">\n <span data-field=\"status\">Ready</span>\n </main>\n</section>\n`;\n}\n\nfunction dashboardCss(): string {\n return `.hcm-mini-app {\n min-height: 100%;\n padding: 24px;\n color: #1f2933;\n background: #f6f8fb;\n font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n}\n\n.hcm-mini-app__header {\n margin-bottom: 16px;\n}\n\n.hcm-mini-app__eyebrow {\n margin: 0 0 4px;\n color: #52606d;\n font-size: 12px;\n}\n\n.hcm-mini-app h1 {\n margin: 0;\n font-size: 24px;\n line-height: 1.25;\n}\n`;\n}\n\nfunction dashboardJs(): string {\n return `export async function mount(container, hcm) {\n const status = container.querySelector('[data-field=\"status\"]');\n if (status) {\n status.textContent = \"Tenant \" + hcm.context.tenantId;\n }\n}\n\nexport function unmount(container) {\n container.innerHTML = \"\";\n}\n`;\n}\n\nfunction multiPageHtml(appCode: string, displayName: string): string {\n return `<section class=\"mini-routed-app\" data-mini-app=\"${appCode}\">\n <header class=\"mini-routed-app__header\">\n <div>\n <p class=\"mini-routed-app__eyebrow\">Tenant <span data-field=\"tenant-id\">-</span></p>\n <h1>${escapeHtml(displayName)}</h1>\n </div>\n <span class=\"mini-routed-app__status\" data-field=\"status\">准备就绪</span>\n </header>\n\n <nav class=\"mini-routed-app__nav\" data-field=\"nav\" aria-label=\"轻应用页面\"></nav>\n <main class=\"mini-routed-app__view\" data-field=\"view\"></main>\n <pre class=\"mini-routed-app__error\" data-field=\"error\" hidden></pre>\n</section>\n`;\n}\n\nfunction multiPageCss(): string {\n return `.mini-routed-app {\n min-height: 100%;\n padding: 24px;\n color: #1f2933;\n background: #f6f8fb;\n font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n}\n\n.mini-routed-app__header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 16px;\n}\n\n.mini-routed-app__eyebrow,\n.mini-routed-app__status {\n margin: 0;\n color: #52606d;\n font-size: 12px;\n}\n\n.mini-routed-app h1 {\n margin: 4px 0 0;\n font-size: 24px;\n line-height: 1.2;\n}\n\n.mini-routed-app__nav {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-bottom: 14px;\n}\n\n.mini-routed-app__nav a {\n border: 1px solid #d9e2ec;\n border-radius: 6px;\n padding: 7px 12px;\n color: #52606d;\n background: #fff;\n text-decoration: none;\n}\n\n.mini-routed-app__nav a[aria-current=\"page\"] {\n border-color: #2f80ed;\n color: #fff;\n background: #2f80ed;\n}\n\n.mini-routed-app__view {\n min-height: 320px;\n border: 1px solid #d9e2ec;\n border-radius: 8px;\n padding: 18px;\n background: #fff;\n}\n\n.mini-routed-app__toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 14px;\n}\n\n.mini-routed-app__grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 12px;\n}\n\n.mini-routed-app__metric,\n.mini-routed-app__section {\n border: 1px solid #e5edf5;\n border-radius: 8px;\n padding: 14px;\n background: #fbfdff;\n}\n\n.mini-routed-app__metric span,\n.mini-routed-app__section span {\n display: block;\n color: #8a94a6;\n font-size: 12px;\n}\n\n.mini-routed-app__metric strong {\n display: block;\n margin-top: 6px;\n font-size: 24px;\n}\n\n.mini-routed-app__list {\n display: grid;\n gap: 8px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.mini-routed-app__list button,\n.mini-routed-app__button {\n height: 34px;\n border: 1px solid #2f80ed;\n border-radius: 6px;\n padding: 0 12px;\n color: #fff;\n background: #2f80ed;\n cursor: pointer;\n}\n\n.mini-routed-app__list button {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n border-color: #d9e2ec;\n color: #1f2933;\n background: #f8fafc;\n text-align: left;\n}\n\n.mini-routed-app__muted {\n color: #8a94a6;\n}\n\n.mini-routed-app__error {\n margin: 14px 0 0;\n border: 1px solid #fecaca;\n border-radius: 6px;\n padding: 10px;\n color: #b91c1c;\n background: #fff1f2;\n white-space: pre-wrap;\n}\n\n@media (max-width: 720px) {\n .mini-routed-app {\n padding: 16px;\n }\n\n .mini-routed-app__header,\n .mini-routed-app__toolbar {\n display: grid;\n }\n}\n`;\n}\n\nfunction multiPageJs(appCode: string, displayName: string, config: MiniReadTemplateConfig): string {\n return `const APP_CODE = ${JSON.stringify(appCode)};\nconst APP_NAME = ${JSON.stringify(displayName)};\nconst DATA_MODEL = ${JSON.stringify(config.model)};\nconst MODEL_LABEL = ${JSON.stringify(config.modelLabel)};\nconst ROUTES = [\n { path: \"/\", label: \"总览\" },\n { path: \"/records\", label: MODEL_LABEL },\n { path: \"/settings\", label: \"设置\" },\n];\n\nconst state = {\n records: [],\n loaded: false,\n details: new Map(),\n unsubscribe: null,\n};\n\nconst textOf = (value, fallback = \"-\") => String(value ?? fallback);\nconst setText = (container, selector, value) => {\n const node = container.querySelector(selector);\n if (node) node.textContent = textOf(value);\n};\nconst setStatus = (container, value) => setText(container, '[data-field=\"status\"]', value);\nconst showError = (container, error) => {\n const node = container.querySelector('[data-field=\"error\"]');\n if (!node) return;\n if (!error) {\n node.hidden = true;\n node.textContent = \"\";\n return;\n }\n node.hidden = false;\n node.textContent = [error.code || \"REQUEST_FAILED\", error.message || \"请求失败\", error.traceId ? \"traceId: \" + error.traceId : \"\"].filter(Boolean).join(\"\\\\n\");\n};\nconst viewOf = (container) => container.querySelector('[data-field=\"view\"]');\nconst createNode = (tag, className, text) => {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (text !== undefined) node.textContent = text;\n return node;\n};\nconst recordName = (record) => record.name || record.employeeName || record.code || record.unitCode || record.positionCode || record.employeeNo || record.id || \"未命名\" + MODEL_LABEL;\n\nconst renderNav = (container, hcm) => {\n const nav = container.querySelector('[data-field=\"nav\"]');\n if (!nav) return;\n const current = hcm.router.current().path;\n nav.innerHTML = \"\";\n for (const route of ROUTES) {\n const link = document.createElement(\"a\");\n link.href = hcm.router.link(route.path);\n link.dataset.route = route.path;\n link.textContent = route.label;\n const active = route.path === \"/\" ? current === \"/\" : current === route.path || current.startsWith(route.path + \"/\");\n if (active) link.setAttribute(\"aria-current\", \"page\");\n nav.appendChild(link);\n }\n};\n\nconst loadRecords = async (container, hcm) => {\n if (state.loaded) return;\n setStatus(container, \"加载中\");\n const result = await hcm.query({ model: DATA_MODEL, page: 0, size: 10 });\n state.records = result.rows || [];\n state.loaded = true;\n setStatus(container, \"已更新\");\n};\n\nconst loadDetail = async (hcm, id) => {\n if (!state.details.has(id)) {\n state.details.set(id, await hcm.get({ model: DATA_MODEL, id }));\n }\n return state.details.get(id);\n};\n\nconst renderOverview = (container, hcm) => {\n const view = viewOf(container);\n if (!view) return;\n view.innerHTML = \"\";\n const title = createNode(\"div\", \"mini-routed-app__toolbar\");\n const heading = createNode(\"h2\", \"\", \"总览\");\n const action = createNode(\"button\", \"mini-routed-app__button\", \"查看\" + MODEL_LABEL);\n action.type = \"button\";\n action.dataset.route = \"/records\";\n title.appendChild(heading);\n title.appendChild(action);\n\n const grid = createNode(\"div\", \"mini-routed-app__grid\");\n const metrics = [\n [\"应用\", APP_NAME],\n [\"数据模型\", DATA_MODEL],\n [\"当前租户\", hcm.context.tenantId],\n ];\n for (const [label, value] of metrics) {\n const card = createNode(\"div\", \"mini-routed-app__metric\");\n card.appendChild(createNode(\"span\", \"\", label));\n card.appendChild(createNode(\"strong\", \"\", value));\n grid.appendChild(card);\n }\n view.appendChild(title);\n view.appendChild(grid);\n};\n\nconst renderRecords = async (container, hcm) => {\n await loadRecords(container, hcm);\n const view = viewOf(container);\n if (!view) return;\n view.innerHTML = \"\";\n const toolbar = createNode(\"div\", \"mini-routed-app__toolbar\");\n toolbar.appendChild(createNode(\"h2\", \"\", MODEL_LABEL));\n toolbar.appendChild(createNode(\"span\", \"mini-routed-app__muted\", \"共 \" + state.records.length + \" 条\"));\n const list = createNode(\"ul\", \"mini-routed-app__list\");\n for (const record of state.records) {\n const item = document.createElement(\"li\");\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.dataset.recordId = textOf(record.id, \"\");\n button.appendChild(createNode(\"strong\", \"\", recordName(record)));\n button.appendChild(createNode(\"span\", \"mini-routed-app__muted\", record.status || record.employeeNo || record.code || \"详情\"));\n item.appendChild(button);\n list.appendChild(item);\n }\n if (!state.records.length) {\n list.appendChild(createNode(\"li\", \"mini-routed-app__muted\", \"暂无\" + MODEL_LABEL + \"记录\"));\n }\n view.appendChild(toolbar);\n view.appendChild(list);\n};\n\nconst renderRecordDetail = async (container, hcm, id) => {\n const record = await loadDetail(hcm, id);\n const view = viewOf(container);\n if (!view) return;\n view.innerHTML = \"\";\n const toolbar = createNode(\"div\", \"mini-routed-app__toolbar\");\n const title = createNode(\"h2\", \"\", recordName(record));\n const back = createNode(\"button\", \"mini-routed-app__button\", \"返回列表\");\n back.type = \"button\";\n back.dataset.route = \"/records\";\n toolbar.appendChild(title);\n toolbar.appendChild(back);\n\n const grid = createNode(\"div\", \"mini-routed-app__grid\");\n const fields = [\n [\"ID\", record.id],\n [\"编号\", record.employeeNo || record.code],\n [\"状态\", record.status],\n [\"邮箱\", record.email],\n ];\n for (const [label, value] of fields) {\n const card = createNode(\"div\", \"mini-routed-app__section\");\n card.appendChild(createNode(\"span\", \"\", label));\n card.appendChild(createNode(\"strong\", \"\", textOf(value)));\n grid.appendChild(card);\n }\n view.appendChild(toolbar);\n view.appendChild(grid);\n};\n\nconst renderSettings = (container, hcm) => {\n const view = viewOf(container);\n if (!view) return;\n view.innerHTML = \"\";\n const section = createNode(\"div\", \"mini-routed-app__section\");\n section.appendChild(createNode(\"h2\", \"\", \"设置\"));\n section.appendChild(createNode(\"span\", \"\", \"应用编码\"));\n section.appendChild(createNode(\"strong\", \"\", APP_CODE));\n section.appendChild(createNode(\"span\", \"\", \"当前路由\"));\n section.appendChild(createNode(\"strong\", \"\", hcm.router.current().path));\n view.appendChild(section);\n};\n\nconst renderRoute = async (container, hcm) => {\n showError(container, null);\n renderNav(container, hcm);\n const detailRoute = hcm.router.match(\"/records/:id\");\n if (detailRoute) {\n await renderRecordDetail(container, hcm, detailRoute.params.id);\n return;\n }\n if (hcm.router.match(\"/records\")) {\n await renderRecords(container, hcm);\n return;\n }\n if (hcm.router.match(\"/settings\")) {\n renderSettings(container, hcm);\n return;\n }\n if (hcm.router.current().path !== \"/\") {\n hcm.router.replace(\"/\");\n return;\n }\n renderOverview(container, hcm);\n};\n\nexport async function mount(container, hcm) {\n setText(container, '[data-field=\"tenant-id\"]', hcm.context.tenantId);\n const onClick = (event) => {\n const routeTarget = event.target instanceof Element ? event.target.closest(\"[data-route]\") : null;\n if (routeTarget?.dataset.route) {\n event.preventDefault();\n hcm.router.push(routeTarget.dataset.route);\n return;\n }\n const recordTarget = event.target instanceof Element ? event.target.closest(\"[data-record-id]\") : null;\n if (recordTarget?.dataset.recordId) {\n hcm.router.push(\"/records/\" + encodeURIComponent(recordTarget.dataset.recordId));\n }\n };\n container.addEventListener(\"click\", onClick);\n state.unsubscribe = hcm.router.subscribe(() => void renderRoute(container, hcm).catch((error) => showError(container, error)));\n await renderRoute(container, hcm);\n container.__miniCleanup = () => {\n container.removeEventListener(\"click\", onClick);\n state.unsubscribe?.();\n };\n}\n\nexport function unmount(container) {\n container.__miniCleanup?.();\n container.innerHTML = \"\";\n}\n`;\n}\n\nfunction writableHtml(appCode: string, displayName: string, config: ReturnType<typeof buildWritableTemplateConfig>): string {\n const modelLabel = escapeHtml(config.modelLabel);\n return `<section class=\"mini-workbench\" data-mini-app=\"${appCode}\">\n <header class=\"mini-workbench__header\">\n <div>\n <p class=\"mini-workbench__eyebrow\">Tenant <span data-field=\"tenant-id\">-</span></p>\n <h1>${escapeHtml(displayName)}</h1>\n </div>\n <span class=\"mini-workbench__status\" data-field=\"status\">准备就绪</span>\n </header>\n\n <div class=\"mini-workbench__layout\">\n <aside class=\"mini-workbench__panel\">\n <div class=\"mini-workbench__panel-head\">\n <strong>${modelLabel}</strong>\n <div class=\"mini-workbench__actions\">\n <button type=\"button\" data-action=\"new-record\">新建</button>\n <button type=\"button\" data-action=\"refresh\">刷新</button>\n </div>\n </div>\n <div class=\"mini-workbench__list\" data-field=\"record-list\"></div>\n </aside>\n\n <main class=\"mini-workbench__panel\">\n <div class=\"mini-workbench__panel-head\">\n <strong data-field=\"editor-title\">选择${modelLabel}</strong>\n <div class=\"mini-workbench__actions\">\n <button type=\"button\" data-action=\"delete-record\" hidden>删除</button>\n <button type=\"button\" data-action=\"back\">返回列表</button>\n </div>\n </div>\n <div class=\"mini-workbench__empty\" data-field=\"editor-empty\">\n <strong>选择一条${modelLabel}</strong>\n <span>从左侧列表打开记录,或点击“新建”。</span>\n </div>\n <form class=\"mini-workbench__form\" data-field=\"editor-form\" hidden>\n <div class=\"mini-workbench__fields\" data-field=\"editor-fields\"></div>\n <button type=\"submit\">保存</button>\n </form>\n <pre class=\"mini-workbench__error\" data-field=\"error\" hidden></pre>\n </main>\n </div>\n</section>\n`;\n}\n\nfunction writableCss(): string {\n return `.mini-workbench {\n min-height: 100%;\n padding: 24px;\n color: #1f2933;\n background: #f6f8fb;\n font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n}\n\n.mini-workbench__header,\n.mini-workbench__panel-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n}\n\n.mini-workbench__header {\n margin-bottom: 18px;\n}\n\n.mini-workbench__eyebrow,\n.mini-workbench__status {\n margin: 0;\n color: #52606d;\n font-size: 12px;\n}\n\n.mini-workbench h1 {\n margin: 4px 0 0;\n font-size: 24px;\n line-height: 1.2;\n}\n\n.mini-workbench__layout {\n display: grid;\n grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);\n gap: 14px;\n}\n\n.mini-workbench__panel {\n min-height: 320px;\n border: 1px solid #d9e2ec;\n border-radius: 8px;\n padding: 16px;\n background: #fff;\n}\n\n.mini-workbench__actions,\n.mini-workbench__form,\n.mini-workbench__fields {\n display: grid;\n gap: 10px;\n}\n\n.mini-workbench__form[hidden],\n.mini-workbench__empty[hidden] {\n display: none;\n}\n\n.mini-workbench__actions {\n grid-auto-flow: column;\n align-items: center;\n}\n\n.mini-workbench button {\n height: 32px;\n border: 1px solid #2f80ed;\n border-radius: 6px;\n padding: 0 12px;\n color: #fff;\n background: #2f80ed;\n cursor: pointer;\n}\n\n.mini-workbench__list {\n display: grid;\n gap: 8px;\n}\n\n.mini-workbench__empty {\n display: grid;\n gap: 8px;\n min-height: 220px;\n place-content: center;\n color: #8a94a6;\n text-align: center;\n}\n\n.mini-workbench__empty strong {\n color: #52606d;\n font-size: 15px;\n}\n\n.mini-workbench__list button {\n width: 100%;\n border-color: #d9e2ec;\n color: #1f2933;\n background: #f8fafc;\n text-align: left;\n}\n\n.mini-workbench__field {\n display: grid;\n gap: 6px;\n color: #52606d;\n font-size: 13px;\n}\n\n.mini-workbench__field input,\n.mini-workbench__field select,\n.mini-workbench__field textarea {\n min-height: 36px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n padding: 0 10px;\n color: #1f2933;\n background: #fff;\n}\n\n.mini-workbench__field textarea {\n min-height: 72px;\n padding: 8px 10px;\n}\n\n.mini-workbench__field--checkbox {\n grid-template-columns: auto 1fr;\n align-items: center;\n}\n\n.mini-workbench__field--checkbox input {\n min-height: auto;\n}\n\n.mini-workbench__field-hint {\n color: #8a94a6;\n font-size: 12px;\n}\n\n.mini-workbench__reference {\n display: grid;\n gap: 6px;\n position: relative;\n}\n\n.mini-workbench__reference-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 8px;\n}\n\n.mini-workbench__reference-results {\n display: grid;\n gap: 4px;\n max-height: 180px;\n overflow: auto;\n border: 1px solid #d9e2ec;\n border-radius: 6px;\n padding: 6px;\n background: #fff;\n}\n\n.mini-workbench__reference-results[hidden] {\n display: none;\n}\n\n.mini-workbench__reference-results button {\n width: 100%;\n border-color: #d9e2ec;\n color: #1f2933;\n background: #f8fafc;\n text-align: left;\n}\n\n.mini-workbench__required {\n color: #dc2626;\n}\n\n.mini-workbench__error {\n margin: 14px 0 0;\n border: 1px solid #fecaca;\n border-radius: 6px;\n padding: 10px;\n color: #b91c1c;\n background: #fff1f2;\n white-space: pre-wrap;\n}\n\n@media (max-width: 720px) {\n .mini-workbench__layout {\n grid-template-columns: 1fr;\n }\n}\n`;\n}\n\nfunction writableJs(config: ReturnType<typeof buildWritableTemplateConfig>): string {\n const model = JSON.stringify(config.model);\n const modelLabel = JSON.stringify(config.modelLabel);\n const fields = config.fields?.length ? JSON.stringify(config.fields) : 'null';\n const defaults = JSON.stringify(config.defaults, null, 2);\n const todayDefaults = JSON.stringify(config.todayDefaultFields);\n return `const MODEL = ${model};\nconst MODEL_LABEL = ${modelLabel};\nconst ROUTE_BASE = \"/records\";\nconst FORM_FIELDS = ${fields};\nconst CREATE_DEFAULTS = ${defaults};\nconst TODAY_DEFAULT_FIELDS = ${todayDefaults};\nconst state = {\n records: [],\n activeId: \"\",\n mode: \"list\",\n meta: null,\n unsubscribe: null,\n};\nconst textOf = (value, fallback = \"-\") => String(value ?? fallback);\nconst today = () => new Date().toISOString().slice(0, 10);\nconst formDefaults = () => {\n const defaults = { ...CREATE_DEFAULTS };\n for (const key of TODAY_DEFAULT_FIELDS) defaults[key] = today();\n return defaults;\n};\nconst formOptions = (defaults = {}) => {\n const options = {};\n if (Array.isArray(FORM_FIELDS) && FORM_FIELDS.length) options.include = FORM_FIELDS;\n if (defaults && Object.keys(defaults).length) options.defaults = defaults;\n return options;\n};\nconst setText = (container, selector, value) => {\n const node = container.querySelector(selector);\n if (node) node.textContent = textOf(value);\n};\nconst setStatus = (container, value) => setText(container, '[data-field=\"status\"]', value);\nconst setDeleteVisible = (container, visible) => {\n const button = container.querySelector('[data-action=\"delete-record\"]');\n if (button) button.hidden = !visible;\n};\nconst setEditorVisible = (container, visible) => {\n const form = container.querySelector('[data-field=\"editor-form\"]');\n const empty = container.querySelector('[data-field=\"editor-empty\"]');\n if (form) form.hidden = !visible;\n if (empty) empty.hidden = visible;\n};\nconst showError = (container, error) => {\n const node = container.querySelector('[data-field=\"error\"]');\n if (!node) return;\n if (!error) {\n node.hidden = true;\n node.textContent = \"\";\n return;\n }\n node.hidden = false;\n node.textContent = [error.code || \"REQUEST_FAILED\", error.message || \"请求失败\", error.traceId ? \"traceId: \" + error.traceId : \"\"].filter(Boolean).join(\"\\\\n\");\n};\nconst ensureMeta = async (hcm) => {\n if (!state.meta) state.meta = await hcm.describe({ model: MODEL, type: \"info\" });\n return state.meta;\n};\nconst queryReferenceRecords = async (hcm, field, keyword) => {\n const model = field.reference?.model;\n if (!model) return [];\n if (hcm.reference?.search) {\n return (await hcm.reference.search({ model, field: field.reference?.field, keyword, size: 8 })).rows;\n }\n const query = { model, page: 0, size: 8 };\n const trimmed = keyword.trim();\n if (trimmed) {\n query.filter = { [field.reference?.field || \"name\"]: { $contains: trimmed } };\n }\n try {\n return (await hcm.query(query)).rows;\n } catch (error) {\n if (!trimmed) throw error;\n return (await hcm.query({ model, page: 0, size: 8 })).rows;\n }\n};\nconst referenceLabel = (hcm, record) => hcm.reference?.label ? hcm.reference.label(record) : record?.name || record?.code || record?.unitCode || record?.positionCode || record?.employeeNo || record?.id || \"未命名记录\";\nconst recordName = (record) => record?.name || record?.employeeName || record?.code || record?.unitCode || record?.positionCode || record?.employeeNo || record?.id || \"未命名\" + MODEL_LABEL;\nconst hydrateReferenceLabel = async (hcm, field, searchInput, value) => {\n const id = value === undefined || value === null ? \"\" : String(value).trim();\n if (!id || !field.reference?.model || !hcm.reference?.labelFor) return;\n const label = await hcm.reference.labelFor({ model: field.reference.model, id });\n if (searchInput.value === id) searchInput.value = label || id;\n};\nconst applyReferenceRecord = (hcm, hiddenInput, searchInput, record) => {\n hiddenInput.value = record?.id ?? \"\";\n searchInput.value = referenceLabel(hcm, record);\n};\nconst isUnsupportedMiniUi = (error) => error?.code === \"MINI_UI_UNSUPPORTED\" || error?.name === \"MiniUiUnsupportedError\";\nconst pickReferenceRecord = async (hcm, field) => {\n const model = field.reference?.model;\n if (!model || !hcm.ui?.pickModel) return null;\n try {\n const result = await hcm.ui.pickModel({\n model,\n multiple: false,\n title: \"选择\" + field.label,\n });\n return result.confirmed ? result.record : null;\n } catch (error) {\n if (isUnsupportedMiniUi(error)) return null;\n throw error;\n }\n};\nconst renderReferenceOptions = (hcm, box, hiddenInput, searchInput, records) => {\n box.innerHTML = \"\";\n if (!records.length) {\n box.textContent = \"暂无可选记录\";\n box.hidden = false;\n return;\n }\n for (const record of records) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.textContent = referenceLabel(hcm, record);\n button.addEventListener(\"click\", () => {\n applyReferenceRecord(hcm, hiddenInput, searchInput, record);\n box.hidden = true;\n });\n box.appendChild(button);\n }\n box.hidden = false;\n};\nconst renderReferenceControl = (field, value, hcm) => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = \"mini-workbench__reference\";\n const hidden = document.createElement(\"input\");\n hidden.type = \"hidden\";\n hidden.name = field.key;\n hidden.dataset.formKey = field.key;\n hidden.value = value ?? \"\";\n const search = document.createElement(\"input\");\n search.type = \"search\";\n search.name = field.key + \"__search\";\n search.autocomplete = \"off\";\n search.placeholder = field.reference?.model ? \"搜索 \" + field.reference.model : \"搜索记录\";\n search.value = value ?? \"\";\n void hydrateReferenceLabel(hcm, field, search, value);\n const picker = document.createElement(\"button\");\n picker.type = \"button\";\n picker.textContent = \"选择\";\n const results = document.createElement(\"div\");\n results.className = \"mini-workbench__reference-results\";\n results.hidden = true;\n let requestId = 0;\n const searchRecords = async () => {\n const current = ++requestId;\n results.hidden = false;\n results.textContent = \"搜索中...\";\n try {\n const records = await queryReferenceRecords(hcm, field, search.value);\n if (current === requestId) renderReferenceOptions(hcm, results, hidden, search, records);\n } catch (error) {\n if (current === requestId) {\n results.textContent = \"搜索失败,可直接填写记录 ID\";\n }\n }\n };\n search.addEventListener(\"focus\", () => void searchRecords());\n search.addEventListener(\"input\", () => {\n hidden.value = search.value.trim();\n void searchRecords();\n });\n picker.addEventListener(\"click\", () => {\n void (async () => {\n results.hidden = true;\n const record = await pickReferenceRecord(hcm, field);\n if (record) {\n applyReferenceRecord(hcm, hidden, search, record);\n return;\n }\n search.focus();\n await searchRecords();\n })().catch((error) => {\n results.hidden = false;\n results.textContent = error?.message || \"选择失败,可直接搜索或填写记录 ID\";\n });\n });\n const row = document.createElement(\"div\");\n row.className = \"mini-workbench__reference-row\";\n row.appendChild(search);\n row.appendChild(picker);\n wrapper.appendChild(hidden);\n wrapper.appendChild(row);\n wrapper.appendChild(results);\n return wrapper;\n};\nconst renderFormControl = (field, value, hcm) => {\n const key = field.key;\n if (field.inputType === \"select\" && field.options?.length) {\n const select = document.createElement(\"select\");\n select.name = key;\n select.dataset.formKey = key;\n for (const option of field.options) {\n const item = document.createElement(\"option\");\n item.value = String(option.value);\n item.textContent = option.label;\n item.disabled = option.disabled === true;\n if (String(option.value) === String(value ?? \"\")) item.selected = true;\n select.appendChild(item);\n }\n return select;\n }\n if (field.inputType === \"textarea\") {\n const textarea = document.createElement(\"textarea\");\n textarea.name = key;\n textarea.dataset.formKey = key;\n textarea.value = value ?? \"\";\n return textarea;\n }\n if (field.inputType === \"reference\") {\n return renderReferenceControl(field, value, hcm);\n }\n const input = document.createElement(\"input\");\n input.name = key;\n input.dataset.formKey = key;\n input.autocomplete = \"off\";\n input.type =\n field.inputType === \"number\" ? \"number\" :\n field.inputType === \"date\" ? \"date\" :\n field.inputType === \"datetime\" ? \"datetime-local\" :\n field.inputType === \"checkbox\" ? \"checkbox\" : \"text\";\n if (field.inputType === \"checkbox\") {\n input.checked = value === true || value === \"true\";\n } else {\n input.value = value ?? \"\";\n }\n return input;\n};\nconst renderForm = async (container, hcm, values = {}) => {\n const meta = await ensureMeta(hcm);\n const defaults = formDefaults();\n const fields = hcm.form.fields(meta, formOptions());\n const fieldBox = container.querySelector('[data-field=\"editor-fields\"]');\n if (!fieldBox) return;\n fieldBox.innerHTML = \"\";\n for (const field of fields) {\n const value = values[field.key] ?? defaults[field.key] ?? field.defaultValue ?? \"\";\n const label = document.createElement(\"label\");\n label.className = \"mini-workbench__field\" + (field.inputType === \"checkbox\" ? \" mini-workbench__field--checkbox\" : \"\");\n const caption = document.createElement(\"span\");\n caption.textContent = field.label;\n if (field.required) {\n const mark = document.createElement(\"strong\");\n mark.className = \"mini-workbench__required\";\n mark.textContent = \" *\";\n caption.appendChild(mark);\n }\n label.appendChild(caption);\n label.appendChild(renderFormControl(field, value, hcm));\n if (field.inputType === \"reference\" && field.reference?.model) {\n const hint = document.createElement(\"small\");\n hint.className = \"mini-workbench__field-hint\";\n hint.textContent = \"关联模型:\" + field.reference.model + \",填写记录 ID\";\n label.appendChild(hint);\n }\n fieldBox.appendChild(label);\n }\n};\nconst readFormValues = (container) => {\n const values = {};\n for (const input of container.querySelectorAll(\"[data-form-key]\")) {\n const key = input.dataset.formKey;\n if (input.type === \"checkbox\") {\n values[key] = input.checked;\n continue;\n }\n const value = input.value?.trim();\n if (value === \"\") continue;\n values[key] = value;\n }\n return values;\n};\nconst renderList = (container, hcm) => {\n const list = container.querySelector('[data-field=\"record-list\"]');\n if (!list) return;\n list.innerHTML = \"\";\n for (const item of state.records) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.dataset.action = \"open-record\";\n button.dataset.id = textOf(item.id, \"\");\n button.textContent = recordName(item);\n list.appendChild(button);\n }\n if (!state.records.length) list.textContent = \"暂无\" + MODEL_LABEL;\n if (hcm.router.current().path === \"/\") hcm.router.replace(ROUTE_BASE);\n};\nconst loadRecords = async (container, hcm) => {\n setStatus(container, \"加载中\");\n showError(container, null);\n const result = await hcm.query({ model: MODEL, page: 0, size: 50 });\n state.records = result.rows;\n renderList(container, hcm);\n setStatus(container, \"已更新\");\n};\nconst openEditor = async (container, hcm, id) => {\n state.activeId = id;\n state.mode = \"edit\";\n setEditorVisible(container, true);\n setStatus(container, \"读取详情\");\n showError(container, null);\n const record = await hcm.get({ model: MODEL, id });\n setText(container, '[data-field=\"editor-title\"]', recordName(record));\n await renderForm(container, hcm, record);\n setDeleteVisible(container, true);\n setStatus(container, \"可编辑\");\n};\nconst openNew = async (container, hcm) => {\n state.activeId = \"\";\n state.mode = \"new\";\n setEditorVisible(container, true);\n showError(container, null);\n setText(container, '[data-field=\"editor-title\"]', \"新建\" + MODEL_LABEL);\n await renderForm(container, hcm, formDefaults());\n setDeleteVisible(container, false);\n setStatus(container, \"可创建\");\n};\nconst renderRoute = async (container, hcm) => {\n if (hcm.router.match(ROUTE_BASE + \"/new\")) {\n await openNew(container, hcm);\n return;\n }\n const route = hcm.router.match(ROUTE_BASE + \"/:id\");\n if (route) {\n await openEditor(container, hcm, route.params.id);\n return;\n }\n state.activeId = \"\";\n state.mode = \"list\";\n setEditorVisible(container, false);\n setText(container, '[data-field=\"editor-title\"]', \"选择\" + MODEL_LABEL);\n const fieldBox = container.querySelector('[data-field=\"editor-fields\"]');\n if (fieldBox) fieldBox.innerHTML = \"\";\n setDeleteVisible(container, false);\n renderList(container, hcm);\n};\nconst saveActive = async (container, hcm) => {\n if (state.mode !== \"new\" && !state.activeId) return;\n const meta = await ensureMeta(hcm);\n const validation = hcm.form.validate(meta, readFormValues(container), {\n ...formOptions(),\n defaults: state.mode === \"new\" ? formDefaults() : {},\n });\n if (!validation.valid) {\n showError(container, { code: \"FORM_INVALID\", message: validation.errors.map((item) => item.message).join(\"\\\\n\") });\n return;\n }\n setStatus(container, state.mode === \"new\" ? \"创建中\" : \"保存中\");\n showError(container, null);\n if (state.mode === \"new\") {\n const created = await hcm.create({ model: MODEL, data: validation.payload });\n await loadRecords(container, hcm);\n hcm.router.push(created?.id ? ROUTE_BASE + \"/\" + encodeURIComponent(created.id) : ROUTE_BASE);\n setStatus(container, \"已创建\");\n return;\n }\n await hcm.update({ model: MODEL, id: state.activeId, data: validation.payload });\n await loadRecords(container, hcm);\n hcm.router.push(ROUTE_BASE + \"/\" + encodeURIComponent(state.activeId));\n setStatus(container, \"已保存\");\n};\nconst deleteActive = async (container, hcm) => {\n if (!state.activeId) return;\n const title = container.querySelector('[data-field=\"editor-title\"]')?.textContent || state.activeId;\n const confirmed = typeof window === \"undefined\" || window.confirm(\"确定删除\" + MODEL_LABEL + \" \" + title + \"?\");\n if (!confirmed) return;\n setStatus(container, \"删除中\");\n showError(container, null);\n await hcm.remove({ model: MODEL, id: state.activeId });\n state.activeId = \"\";\n state.mode = \"list\";\n await loadRecords(container, hcm);\n hcm.router.push(ROUTE_BASE);\n setStatus(container, \"已删除\");\n};\nexport async function mount(container, hcm) {\n setText(container, '[data-field=\"tenant-id\"]', hcm.context.tenantId);\n const onClick = (event) => {\n const target = event.target instanceof Element ? event.target.closest(\"[data-action]\") : null;\n if (!target) return;\n if (target.dataset.action === \"refresh\") void loadRecords(container, hcm).catch((error) => showError(container, error));\n if (target.dataset.action === \"new-record\") hcm.router.push(ROUTE_BASE + \"/new\");\n if (target.dataset.action === \"open-record\" && target.dataset.id) hcm.router.push(ROUTE_BASE + \"/\" + encodeURIComponent(target.dataset.id));\n if (target.dataset.action === \"back\") hcm.router.push(ROUTE_BASE);\n if (target.dataset.action === \"delete-record\") {\n void deleteActive(container, hcm).catch((error) => {\n setStatus(container, \"删除失败\");\n showError(container, error);\n });\n }\n };\n const onSubmit = (event) => {\n event.preventDefault();\n void saveActive(container, hcm).catch((error) => {\n setStatus(container, \"保存失败\");\n showError(container, error);\n });\n };\n container.addEventListener(\"click\", onClick);\n container.querySelector('[data-field=\"editor-form\"]')?.addEventListener(\"submit\", onSubmit);\n state.unsubscribe = hcm.router.subscribe(() => void renderRoute(container, hcm).catch((error) => showError(container, error)));\n await ensureMeta(hcm);\n await loadRecords(container, hcm);\n await renderRoute(container, hcm);\n container.__miniCleanup = () => {\n container.removeEventListener(\"click\", onClick);\n container.querySelector('[data-field=\"editor-form\"]')?.removeEventListener(\"submit\", onSubmit);\n state.unsubscribe?.();\n };\n}\n\nexport function unmount(container) {\n container.__miniCleanup?.();\n container.innerHTML = \"\";\n}\n`;\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 模型缓存运维(两级缓存:Caffeine 本地 + Redis 远程)。\n *\n * 后端契约(CommonModelQueryCacheController):\n * - `GET /api/models/{model}/cache` 统计\n * - `DELETE /api/models/{model}/cache` 清全部实体缓存\n * - `DELETE /api/models/{model}/cache/{id}` 清单条\n * - `DELETE /api/models/{model}/meta/cache` 清元数据缓存(Code 级 YAML + Action 定义)\n *\n * 🔴 缓存必须走 API 管理,不直接操作 Redis —— 保证一致性、审计与业务规则生效。\n */\n\nexport interface CacheStats {\n modelKey: string;\n [k: string]: unknown;\n}\n\nexport interface CacheClearResult {\n modelKey: string;\n clearedCount?: number;\n message?: string;\n [k: string]: unknown;\n}\n\nexport async function getCacheStats(http: AxiosInstance, model: string): Promise<CacheStats> {\n const resp = await http.get(`/api/models/${model}/cache`);\n return (resp.data?.data ?? resp.data) as CacheStats;\n}\n\nexport async function clearModelCache(\n http: AxiosInstance,\n model: string,\n id?: string,\n): Promise<CacheClearResult> {\n const url = id ? `/api/models/${model}/cache/${id}` : `/api/models/${model}/cache`;\n const resp = await http.delete(url);\n return (resp.data?.data ?? resp.data) as CacheClearResult;\n}\n\nexport interface ClearMetaCacheOpts {\n /** 元数据类型(list / info / view / filter / page…),省略 = 全清 */\n type?: string;\n /** 状态变体(如 self),配合 type 使用 */\n state?: string;\n}\n\nexport async function clearMetaCache(\n http: AxiosInstance,\n model: string,\n opts: ClearMetaCacheOpts = {},\n): Promise<CacheClearResult> {\n const params: Record<string, string> = {};\n if (opts.type) params.type = opts.type;\n if (opts.state) params.state = opts.state;\n const resp = await http.delete(`/api/models/${model}/meta/cache`, { params });\n return (resp.data?.data ?? resp.data) as CacheClearResult;\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 租户开通(bootstrap)—— 交付旅程第一步。\n *\n * 后端契约(TenantBootstrapController):\n * - `POST /api/bootstrap/tenant`,header `X-Bootstrap-Key: <secret>`\n * - body `{ tenantId, adminUsername, adminPassword, tenantName? }`\n *\n * 🔴 该端点不走普通 Bearer 认证,用独立的 bootstrap key —— 它要在「还没有任何\n * 租户和用户」的时刻可用。key 由部署方持有,绝不写进 skill 正文或提交进仓库。\n */\n\nexport interface BootstrapTenantRequest {\n tenantId: string;\n adminUsername: string;\n adminPassword: string;\n tenantName?: string;\n}\n\nexport interface BootstrapReconcileResult {\n component?: string;\n version?: number;\n status?: string;\n [k: string]: unknown;\n}\n\nexport interface BootstrapTenantResult {\n success: boolean;\n tenantId: string;\n adminUserId?: string;\n message?: string;\n results?: BootstrapReconcileResult[];\n}\n\nexport async function bootstrapTenant(\n http: AxiosInstance,\n bootstrapKey: string,\n req: BootstrapTenantRequest,\n): Promise<BootstrapTenantResult> {\n const resp = await http.post('/api/bootstrap/tenant', req, {\n headers: { 'X-Bootstrap-Key': bootstrapKey },\n });\n return (resp.data?.data ?? resp.data) as BootstrapTenantResult;\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 租户元数据读写(TenantMetaController,`/api/tenant-meta`)。\n *\n * 统一走 query-param 形态(`?path=`)而非路径通配 `/meta/**`:meta 路径含斜杠与点,\n * query param 避免 URL 段解析歧义与路径穿越面。\n *\n * 后端契约:\n * - `GET /api/tenant-meta/list?keyword=&type=&page=&pageSize=`\n * - `GET /api/tenant-meta/meta?path=<metaName>`\n * - `PUT /api/tenant-meta/meta?path=<metaName>` body `{ content: \"<原文>\" }`\n * - `DELETE /api/tenant-meta/meta?path=<metaName>`\n *\n * 🔴 写入需 config-admin 身份,且 source 必须可写(租户开发空间绑定态)——\n * 后端 `assertConfigAdmin` / `assertWritableSource` 会拒绝越权与只读源。\n */\n\nexport interface TenantMetaEntry {\n metaName?: string;\n name?: string;\n type?: string;\n lastModified?: string;\n size?: number;\n [k: string]: unknown;\n}\n\nexport interface ListTenantMetaOpts {\n keyword?: string;\n /** yml / properties / all(默认 all) */\n type?: string;\n page?: number;\n pageSize?: number;\n}\n\nexport async function listTenantMeta(\n http: AxiosInstance,\n opts: ListTenantMetaOpts = {},\n): Promise<{ items: TenantMetaEntry[]; total?: number; raw: unknown }> {\n const resp = await http.get('/api/tenant-meta/list', { params: opts });\n const payload = resp.data?.data ?? resp.data;\n const items = (payload?.items ?? payload?.list ?? payload?.records ?? []) as TenantMetaEntry[];\n return { items, total: payload?.total, raw: payload };\n}\n\n/** 读单份 meta 原文。返回 `content` 字符串(后端包在信封里时自动解包)。 */\nexport async function getTenantMeta(http: AxiosInstance, path: string): Promise<string> {\n const resp = await http.get('/api/tenant-meta/meta', { params: { path } });\n const payload = resp.data?.data ?? resp.data;\n if (typeof payload === 'string') return payload;\n return String(payload?.content ?? '');\n}\n\nexport interface SaveMetaResult {\n success?: boolean;\n message?: string;\n [k: string]: unknown;\n}\n\nexport async function saveTenantMeta(\n http: AxiosInstance,\n path: string,\n content: string,\n): Promise<SaveMetaResult> {\n const resp = await http.put('/api/tenant-meta/meta', { content }, { params: { path } });\n return (resp.data?.data ?? resp.data) as SaveMetaResult;\n}\n\nexport async function deleteTenantMeta(\n http: AxiosInstance,\n path: string,\n): Promise<SaveMetaResult> {\n const resp = await http.delete('/api/tenant-meta/meta', { params: { path } });\n return (resp.data?.data ?? resp.data) as SaveMetaResult;\n}\n","import type { AxiosInstance } from 'axios';\nimport yaml, { JSON_SCHEMA } from 'js-yaml';\nimport { CliError } from './errors/cli-error';\nimport { CliErrorCode } from './errors/codes';\n\n/**\n * 睿戎 Chiron 交付技能库客户端。\n *\n * <p>🔴 这些端点**免认证**——交付顾问接手的是「还没有租户、还没有账号」的空环境,\n * 必须先装技能才能建租户。所以本模块只需要 endpoint,不碰 identity/token。\n *\n * 后端契约(ChironController):\n * - `GET {base}/skills.json` 目录 manifest\n * - `GET {base}/skills/{id}.md` 技能原文\n * - `GET {base}/skills/{id}/references/{path}` 技能自带的参考文件\n * - `GET {base}/skills/{id}/files?path=...` 完整 Skill 目录中的原始文件\n *\n * 🔴 `{base}` 是 `'/api'` 或 `''`,由 {@link resolveChironBase} 探出来。真实部署只把\n * `/api/**` 路由给后端,根路径归前端 SPA——`GET /skills.json` 在客户环境上拿回来的是\n * 一段 HTML。而老版本后端只有根路径。两种都要能连上,所以先探再用,不写死。\n */\n\nexport interface ChironSkillFile {\n path: string;\n bytes: number;\n sha256: string;\n}\n\nexport interface ChironSkill {\n id: string;\n title: string;\n category: string;\n /** cli = 动作面已全改写为 hcm 命令;curl = 仍为原始 HTTP 形态 */\n actionSurface: 'cli' | 'curl';\n description: string;\n /** 交付生命周期阶段(多值)——一个技能天然跨阶段 */\n stages: string[];\n /** 安装本技能前必须同时安装的其他 Chiron 技能 id;旧服务端缺省时视为空。 */\n requires?: string[];\n bytes: number;\n /** 新服务端提供完整 Skill 目录;旧服务端缺省时只消费 SKILL.md。 */\n files?: ChironSkillFile[];\n markdownUrl: string;\n /**\n * 技能自带的参考文件相对路径(相对 `<skill>/references/`)。SKILL.md 正文用\n * `[x](references/x.md)` 引它们——不跟着装下来就是一堆死链。旧服务端缺省时视为空。\n */\n references?: string[];\n}\n\nexport interface ChironStage {\n key: string;\n name: string;\n description: string;\n /** 该阶段下的技能数。0 = 交付路径上的覆盖缺口 */\n skillCount: number;\n}\n\nexport interface ChironCategory {\n key: string;\n name: string;\n description: string;\n skills: ChironSkill[];\n}\n\nexport interface ChironManifest {\n name: string;\n title: string;\n tagline: string;\n skillCount: number;\n stages: ChironStage[];\n categories: ChironCategory[];\n}\n\n/** 技能库端点的前缀候选,按优先级排列。`/api` 在前:它是真实部署下唯一到得了后端的那条。 */\nconst CHIRON_BASE_CANDIDATES = ['/api', ''] as const;\n\nfunction looksLikeManifest(data: unknown): data is ChironManifest {\n if (!data || typeof data !== 'object') return false;\n const m = data as Record<string, unknown>;\n return m.name === 'chiron' && Array.isArray(m.categories);\n}\n\n/**\n * 探出本环境技能库的前缀。\n *\n * 为什么不能写死任何一个:\n * - 真实部署(反代把 `/api/**` 给后端、其余给前端)下根路径返回的是前端 SPA 的 HTML,\n * HTTP 200 但根本不是 manifest —— 所以判据必须看**内容**,不能看状态码;\n * - 还没升级到带 `/api` 别名那一版的后端,只有根路径可用。\n *\n * 两条都探不到时抛出可执行的错误,而不是把一段 HTML 当目录解析出满屏乱码。\n */\nexport async function resolveChironBase(http: AxiosInstance): Promise<string> {\n const tried: string[] = [];\n for (const base of CHIRON_BASE_CANDIDATES) {\n const url = `${base}/skills.json`;\n tried.push(url);\n try {\n const resp = await http.get(url);\n const data = resp.data?.data ?? resp.data;\n if (looksLikeManifest(data)) return base;\n } catch {\n // 401 / 404 / 网络错误都只说明这条前缀不通,继续探下一条\n }\n }\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message:\n `这个环境上找不到睿戎技能库(试过 ${tried.join(' 和 ')})。`\n + '常见原因:① 后端版本还没有 /api/skills 端点,升级后端即可;'\n + '② endpoint 填的是前端地址且未反代到后端;③ 地址或网络不通。',\n });\n}\n\nexport async function fetchSkillCatalog(http: AxiosInstance, base: string): Promise<ChironManifest> {\n const resp = await http.get(`${base}/skills.json`);\n return (resp.data?.data ?? resp.data) as ChironManifest;\n}\n\nexport async function fetchSkillMarkdown(http: AxiosInstance, id: string, base: string): Promise<string> {\n const resp = await http.get(`${base}/skills/${encodeURIComponent(id)}.md`, {\n responseType: 'text',\n transformResponse: [(d) => d],\n });\n return String(resp.data ?? '');\n}\n\n/** 取完整 Skill 目录中的任意原始文件;`relativePath` 始终按 query 参数传递。 */\nexport async function fetchSkillFile(\n http: AxiosInstance,\n id: string,\n relativePath: string,\n base: string,\n): Promise<Buffer> {\n assertSafeSkillId(id);\n assertSafeSkillFilePath(relativePath);\n const resp = await http.get(`${base}/skills/${encodeURIComponent(id)}/files`, {\n params: { path: relativePath },\n responseType: 'arraybuffer',\n transformResponse: [(data) => data],\n });\n return Buffer.from(resp.data ?? []);\n}\n\n/** 取技能自带的参考文件原文。`relativePath` 相对 `<skill>/references/`。 */\nexport async function fetchSkillReference(\n http: AxiosInstance,\n id: string,\n relativePath: string,\n base: string,\n): Promise<string> {\n assertSafeReferencePath(relativePath);\n const encoded = relativePath.split('/').map(encodeURIComponent).join('/');\n const resp = await http.get(`${base}/skills/${encodeURIComponent(id)}/references/${encoded}`, {\n responseType: 'text',\n transformResponse: [(d) => d],\n });\n return String(resp.data ?? '');\n}\n\n/**\n * 参考文件相对路径约束——与后端 `ChironCatalogService.SAFE_REFERENCE_PATH` 同形。\n *\n * 🔴 服务端返回什么,本地就按什么建目录,所以这里是**写文件前**的闸:`..` 段会让\n * 安装写出技能目录之外。不要因为「服务端已经校验过」就省掉——CLI 可以指向任何 endpoint。\n */\nexport function assertSafeReferencePath(relativePath: string): void {\n const ok = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,63}(\\/[A-Za-z0-9_][A-Za-z0-9._-]{0,63}){0,2}$/\n .test(relativePath);\n if (!ok) {\n throw invalid(`参考文件路径不合法:${JSON.stringify(relativePath)}`);\n }\n}\n\n/** 完整 Skill 目录相对路径约束;允许有限深度之外的目录,但拒绝任何穿越和反斜线。 */\nexport function assertSafeSkillFilePath(relativePath: string): void {\n const segments = relativePath?.split('/') ?? [];\n if (\n !relativePath ||\n relativePath.length > 512 ||\n relativePath.startsWith('/') ||\n relativePath.includes('\\\\') ||\n segments.some(\n (segment) =>\n !segment ||\n segment === '.' ||\n segment === '..' ||\n !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(segment),\n )\n ) {\n throw invalid(`非法 Skill 文件路径: \"${relativePath}\"`);\n }\n}\n\n/** 归一 references 字段:旧服务端缺省时视为空,逐条过白名单。 */\nexport function normalizeReferences(raw: unknown, skillId: string): string[] {\n if (raw === undefined || raw === null) return [];\n if (!Array.isArray(raw)) throw invalid(`技能 ${skillId} 的 references 只能是字符串数组`);\n const out: string[] = [];\n for (const value of raw) {\n if (typeof value !== 'string' || !value.trim()) {\n throw invalid(`技能 ${skillId} 的 references 只能是非空字符串数组`);\n }\n const rel = value.trim();\n assertSafeReferencePath(rel);\n if (!out.includes(rel)) out.push(rel);\n }\n return out;\n}\n\nexport function flattenSkills(manifest: ChironManifest): ChironSkill[] {\n return (manifest.categories ?? []).flatMap((c) => c.skills ?? []);\n}\n\n/**\n * 计算技能安装闭包,返回稳定的「依赖在前、目标在后」顺序。\n *\n * 这是纯函数:远程安装与测试共用,不在这里发请求或写文件。旧目录没有 requires 时\n * 自然退化为只安装目标技能。\n */\nexport function resolveSkillInstallOrder(manifest: ChironManifest, targetId: string): ChironSkill[] {\n const byId = new Map<string, ChironSkill>();\n for (const skill of flattenSkills(manifest)) {\n if (byId.has(skill.id)) {\n throw invalid(`技能目录存在重复 id: ${skill.id}`);\n }\n byId.set(skill.id, skill);\n }\n if (!byId.has(targetId)) {\n throw invalid(`技能目录中不存在 ${targetId}`);\n }\n\n const state = new Map<string, 'visiting' | 'done'>();\n const path: string[] = [];\n const order: ChironSkill[] = [];\n\n const visit = (id: string, requiredBy?: string): void => {\n const current = state.get(id);\n if (current === 'done') return;\n if (current === 'visiting') {\n const cycleStart = path.indexOf(id);\n const cycle = [...path.slice(Math.max(0, cycleStart)), id];\n throw invalid(`技能依赖存在环: ${cycle.join(' -> ')}`);\n }\n\n const skill = byId.get(id);\n if (!skill) {\n throw invalid(`技能 ${requiredBy ?? targetId} 依赖不存在: ${id}`);\n }\n assertSafeSkillId(skill.id);\n state.set(id, 'visiting');\n path.push(id);\n for (const required of normalizeRequirements(skill.requires, skill.id)) {\n visit(required, skill.id);\n }\n path.pop();\n state.set(id, 'done');\n order.push(skill);\n };\n\n visit(targetId);\n return order;\n}\n\n/**\n * 全目录的安装顺序:逐个技能求依赖闭包后合并去重,依赖仍排在被依赖者之前。\n *\n * `install --all` 用它。顾问不必知道谁依赖谁,也不该为了装全而写 shell 循环——\n * 那个循环在 zsh 下 `for id in $(...)` 不分词,会一个都不装却退出码 0。\n */\nexport function resolveAllSkillsInstallOrder(manifest: ChironManifest): ChironSkill[] {\n const order = new Map<string, ChironSkill>();\n const all = flattenSkills(manifest).map((skill) => skill.id).sort();\n for (const id of all) {\n for (const skill of resolveSkillInstallOrder(manifest, id)) {\n if (!order.has(skill.id)) order.set(skill.id, skill);\n }\n }\n return [...order.values()];\n}\n\n/** 读 SKILL.md 的 frontmatter;没有 frontmatter 返回空对象,YAML 非法则报错。 */\nexport function parseSkillFrontmatter(markdown: string, skillId: string): Record<string, unknown> {\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/.exec(markdown);\n if (!match) return {};\n\n let frontmatter: unknown;\n try {\n frontmatter = yaml.load(match[1] ?? '', { schema: JSON_SCHEMA });\n } catch (cause) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: `技能 ${skillId} 的 frontmatter 不是合法 YAML`,\n cause,\n });\n }\n if (!frontmatter || typeof frontmatter !== 'object' || Array.isArray(frontmatter)) return {};\n return frontmatter as Record<string, unknown>;\n}\n\n/** 从一份 SKILL.md 读取本地安装所需的 requires,不接受正文中的近似语法。 */\nexport function parseSkillRequirements(markdown: string, skillId: string): string[] {\n return normalizeRequirements(parseSkillFrontmatter(markdown, skillId).requires, skillId);\n}\n\n/** Chiron 与公开端点共用的小写安全 id 约束。 */\nexport function assertSafeSkillId(id: string): void {\n if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(id)) {\n throw invalid(`技能 id 只能是小写字母/数字/连字符,收到 \"${id}\"`);\n }\n}\n\nfunction normalizeRequirements(raw: unknown, skillId: string): string[] {\n if (raw === undefined || raw === null) return [];\n const values = Array.isArray(raw) ? raw : [raw];\n const normalized: string[] = [];\n for (const value of values) {\n if (typeof value !== 'string' || !value.trim()) {\n throw invalid(`技能 ${skillId} 的 requires 只能是非空技能 id 数组`);\n }\n const id = value.trim();\n assertSafeSkillId(id);\n if (!normalized.includes(id)) normalized.push(id);\n }\n return normalized;\n}\n\nfunction invalid(message: string): CliError {\n return new CliError({ code: CliErrorCode.INVALID_ARGUMENT, message });\n}\n\n/** 按交付生命周期阶段筛选。 */\nexport function filterByStage(skills: ChironSkill[], stage?: string): ChironSkill[] {\n if (!stage) return skills;\n return skills.filter((s) => (s.stages ?? []).includes(stage));\n}\n\n/** 关键词匹配 id / 标题 / 描述 / 分类,全部小写包含。 */\nexport function matchSkills(skills: ChironSkill[], keyword?: string): ChironSkill[] {\n if (!keyword) return skills;\n const k = keyword.toLowerCase();\n return skills.filter((s) =>\n [s.id, s.title, s.description, s.category].some((f) => String(f ?? '').toLowerCase().includes(k)),\n );\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 租户系统参数(配置中心)。\n *\n * <p>后端契约(TenantSettingDomainController):\n * <ul>\n * <li>`GET /api/tenant-settings/domains/{domain}` 读某域(定义 + 租户覆盖值合并,缺失回退默认值)</li>\n * <li>`PATCH /api/tenant-settings/domains/{domain}` 写,body `{items:[{namespace,settingKey,value}]}`</li>\n * <li>`DELETE /api/tenant-settings/domains/{domain}/items/{ns}/{key}?revision=N` 清除租户覆盖</li>\n * </ul>\n *\n * <p>🔴 **不要走标准 Model 的 PUT 改这些值**:那条路直接写库,绕过定义校验、\n * 乐观锁与历史留痕——改坏 namespace/valueType 会让记录不再匹配任何定义。\n * 后端已从该 Model 的权限里移除 update action,这里也只暴露上面三条受控路径。\n */\n\nexport interface SettingItem {\n namespace: string;\n settingKey: string;\n value?: unknown;\n valueType?: string;\n defaultValue?: unknown;\n revision?: number;\n label?: string;\n [k: string]: unknown;\n}\n\nexport async function getSettingDomain(\n http: AxiosInstance,\n domain: string,\n): Promise<{ items: SettingItem[]; raw: unknown }> {\n const resp = await http.get(`/api/tenant-settings/domains/${encodeURIComponent(domain)}`);\n const payload = resp.data?.data ?? resp.data;\n const items = (payload?.items ?? payload ?? []) as SettingItem[];\n return { items: Array.isArray(items) ? items : [], raw: payload };\n}\n\nexport interface SettingWrite {\n namespace: string;\n settingKey: string;\n value: unknown;\n}\n\nexport async function patchSettingDomain(\n http: AxiosInstance,\n domain: string,\n items: SettingWrite[],\n): Promise<unknown> {\n const resp = await http.patch(`/api/tenant-settings/domains/${encodeURIComponent(domain)}`, { items });\n return resp.data?.data ?? resp.data;\n}\n\nexport async function resetSettingItem(\n http: AxiosInstance,\n domain: string,\n namespace: string,\n settingKey: string,\n revision?: number,\n): Promise<unknown> {\n const url =\n `/api/tenant-settings/domains/${encodeURIComponent(domain)}` +\n `/items/${encodeURIComponent(namespace)}/${encodeURIComponent(settingKey)}`;\n const resp = await http.delete(url, {\n params: revision === undefined ? {} : { revision },\n });\n return resp.data?.data ?? resp.data;\n}\n\n/**\n * 解析 `namespace.settingKey=value` 形态的 --set 参数。\n *\n * settingKey 取最后一段,其余是 namespace —— namespace 本身含点\n * (如 `security.password`),所以从右边切,不能 split('.') 取前两段。\n */\nexport function parseSettingAssignment(raw: string): SettingWrite {\n const eq = raw.indexOf('=');\n if (eq <= 0) throw new Error(`--set 需要 namespace.key=value 形态,收到 \"${raw}\"`);\n const path = raw.slice(0, eq).trim();\n const value = raw.slice(eq + 1);\n const dot = path.lastIndexOf('.');\n if (dot <= 0) throw new Error(`--set 的键需含 namespace,如 security.password.passwordPolicy=complex,收到 \"${path}\"`);\n return { namespace: path.slice(0, dot), settingKey: path.slice(dot + 1), value };\n}\n","/**\n * FDE 侦察产物的目录布局与路径遏制。\n *\n * 产物按**爬取身份**分叉:`<root>/recon/<tenantId>/<sourceKey>/…`。\n * 同一个租户、同一条命令,两个 FDE 顾问绑在不同 workspace 上时看到的元数据是不同的——\n * 把两份产物混在一个目录里,后来者不是「挤在一起」,而是**静默覆盖**前一个人的\n * `models.json` / `meta/*.json`,而 `fingerprint.json` 里只剩最后写入者的 `extensionSource`。\n *\n * 移植自 `hcm_nx_core` 的 `tools/fde-kit/hcmnx_kit/layout.py`(ADR-319 刀 6b)。\n * 行为基线是 `docs/superpowers/specs/2026-08-28-fde-kit-behavior-contract.md` §1.3 的 19 条 P。\n */\nimport { createHash } from 'node:crypto';\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport * as path from 'node:path';\n\nconst UNSAFE = /[^A-Za-z0-9._-]/g;\n\n/** 把不安全字符换成下划线。**不保证单射**,只用于人眼可读的那一半。 */\nfunction slug(value: string): string {\n return (value || 'unknown').replace(UNSAFE, '_');\n}\n\n/**\n * sourceKey 实测形如 `ws:poc-cnpc`;冒号等字符不能直接进路径。\n *\n * 🔴 纯字符替换做不到单射(`ws:alice` 与 `ws_alice` 会撞进同一个目录),而不同 sourceKey\n * 的产物不可互换——撞目录不是「挤在一起」,是后跑的 FDE 静默覆盖前一个 FDE 的\n * models.json / meta/*.json。所以在可读 slug 后面缀上**原始键**的短摘要:\n * slug 用来给人看,后缀用来保证身份。\n */\nexport function safeSegment(value: string): string {\n const raw = value || 'unknown';\n const digest = createHash('sha256').update(raw, 'utf8').digest('hex').slice(0, 10);\n return `${slug(raw)}-${digest}`;\n}\n\n/**\n * 租户段的归一。\n *\n * 🔴 **它就是 `safeSegment`,不是另一个归一函数。** 保留独立函数名只为让调用点读起来\n * 有语义(也给命令层一个公开入口,两端才不会各自拼路径),实现必须**原样委派**,\n * 绝不允许两个归一算法并存。\n *\n * ⚠️ 不要改成「校验 tenantId 字符集、非法即拒」:那会在某个 tenantId 真含特殊字符的\n * 环境上**硬拒一个合法环境**,把可用性押在我们对 tenantId 取值范围的猜测上。\n * 摘要后缀不拒绝任何输入,且无条件单射。\n */\nexport function tenantSegment(value: string): string {\n return safeSegment(value);\n}\n\n/**\n * `p` 解析后是否落在 `container` 之内(含相等)。\n *\n * 🔴 **判分量,不判前缀**。写成 `rel.startsWith('..')` 会误杀两类合法路径,\n * 且两类都真实存在于本模块:\n * · `..foo.json` / `...json` —— 合法文件名(契约 §1.3「仅以点开头的文件名不被误杀」);\n * · `..-<摘要>/` —— `safeSegment('..')` 的产物,租户 id 恰好是 `..` 时的正常目录名。\n * 移植时这一格栽过一次:三条用例同时红,根因都在这个前缀判据上。\n */\nfunction isInside(p: string, container: string): boolean {\n const rel = path.relative(container, p);\n if (rel === '') return true;\n if (path.isAbsolute(rel)) return false;\n return !rel.split(path.sep).includes('..');\n}\n\n/**\n * 判 `rel` 里有没有 `..` **分量**。\n *\n * 🔴 判分量而不是字符串 `includes`:`..foo.json` / `...json` 是合法文件名,\n * 字符串判据会把它们误杀。\n *\n * 🔴 `/` **和** `\\` 都当分隔符:`valuedomain/..\\evil.json`、`valuedomain\\..\\evil.json`\n * 这两种形态也必须判得出 `..` 分量。只按 POSIX 分隔符切对它们恒返 false,是个盲区。\n * 收益不是「支持 Windows」,是**多认一种分隔符、少一个盲区**。\n */\nfunction hasDotDotComponent(rel: string): boolean {\n return rel.split(/[/\\\\]/).includes('..');\n}\n\nexport class ReconLayout {\n readonly root: string;\n readonly tenantId: string;\n readonly sourceKey: string;\n readonly dir: string;\n\n constructor(root: string, tenantId: string, sourceKey: string) {\n this.root = root;\n this.tenantId = String(tenantId);\n this.sourceKey = sourceKey;\n // 🔴 产物按爬取身份分叉:不同 sourceKey 的产物不可互换。\n // 两段都走 `safeSegment`——单射由摘要后缀无条件保证。\n this.dir = path.join(\n this.root,\n 'recon',\n tenantSegment(this.tenantId),\n safeSegment(sourceKey),\n );\n // 🔴 容器断言**不是装饰品**:它守的前提是「**归一函数没被改弱**」。\n // 把 `safeSegment` 退回纯字符替换,`--tenant \"..\"` 立刻让产物根跳出 recon/:\n // `<out>/recon/../active-xxx` 规范化后是 `<out>/active-xxx`。\n // `resolvePath` 的闸拦不住这一步——那道闸只比 `this.dir`,而 `this.dir` 自己已经偏了。\n const container = path.resolve(this.root, 'recon');\n if (!isInside(path.resolve(this.dir), container)) {\n throw new LayoutContainmentError(\n `租户段越界,拒绝构造产物目录:tenantId=${JSON.stringify(tenantId)}(产物根 ${container})`,\n );\n }\n }\n\n /**\n * 把相对路径解析成绝对路径,并过两道**互相印证**的遏制闸。\n *\n * 🔴 `rel` 里会嵌入**现爬的 modelKey**:`meta/<k>.model.json`、`valuedomain/<k>.json`、\n * `mapping/<k>.json`。服务端说什么,这里就拼什么。\n *\n * 实测分档(沿用 Python 侧 2026-08-16 的实跑结论):\n * `../evil` → 规范化后**仍在产物根内**,写成 `<产物根>/evil.json`。第二道闸**拦不住**。\n * `../models` → 同上,**顶掉 recon 自己的模型名册**,写入方拿到「成功」,产物里没有留痕。\n * `../../evil` → 真的逃出产物根 ⇒ 第二道闸拦得住。\n * `/etc/passwd` → 前导 `/` 被路径规范化吃掉,落在 `valuedomain/etc/passwd.json`。\n * ⇒ 真正的危害不是「逃出产物根」(那一档本来就拦得住),是「**逃出预期子目录、\n * 静默顶掉同级产物**」——`models.json` / `fingerprint.json` / `gates.json` 全在射程内。\n *\n * ⚠️ 拒绝而不是消毒:消毒会让 `meta/<modelKey>.json` 不再能按 modelKey grep 回去。\n */\n resolvePath(rel: string, createParents = true): string {\n const p = path.join(this.dir, rel);\n // 第一道闸:守「逃出预期子目录」。\n if (hasDotDotComponent(rel)) {\n throw new LayoutContainmentError(\n `产物相对路径含 \\`..\\` 分量,拒绝写入:${JSON.stringify(rel)}` +\n `(会越出预期子目录、静默顶掉同级产物;产物根 ${this.dir})`,\n );\n }\n // 第二道闸:守「逃出产物根」。\n if (!isInside(path.resolve(p), path.resolve(this.dir))) {\n throw new LayoutContainmentError(\n `产物相对路径越界,拒绝写入:${JSON.stringify(rel)}(产物根 ${this.dir})`,\n );\n }\n if (createParents) {\n // 🔴 读路径传 createParents=false:读一个不存在的产物不该顺手建出目录,\n // 否则一个空目录会冒充「这里爬过」——那是必须分开的两件事。\n mkdirSync(path.dirname(p), { recursive: true });\n }\n // 🔴 返回未 realpath 的 p:macOS 上 /var → /private/var 的符号链接会让\n // 调用方拿 this.dir 拼出来的路径断言全部错位。\n return p;\n }\n\n writeJson(rel: string, obj: unknown): string {\n const p = this.resolvePath(rel);\n // JSON.stringify 默认不转义非 ASCII——中文逐字落盘,本地 grep 才找得到。\n writeFileSync(p, JSON.stringify(obj, null, 2), 'utf8');\n return p;\n }\n\n writeJsonl(rel: string, rows: Iterable<unknown>): string {\n const p = this.resolvePath(rel);\n let body = '';\n for (const row of rows) body += `${JSON.stringify(row)}\\n`;\n writeFileSync(p, body, 'utf8');\n return p;\n }\n\n writeText(rel: string, text: string): string {\n const p = this.resolvePath(rel);\n writeFileSync(p, text, 'utf8');\n return p;\n }\n\n /**\n * 逐行读一份 JSONL 产物。\n *\n * 🔴 与 `readJson` 同走那两道越界闸——同一个收口点不能一半有闸一半没有。\n * 🔴 **跳过空行**(末尾换行是 `writeJsonl` 必然产生的),但**不跳过坏行**:\n * 坏行原样抛,因为「这一行读不出来」和「这个产物没有这一行」是两件事。\n */\n readJsonlRows(rel: string): unknown[] {\n const body = readFileSync(this.resolvePath(rel, false), 'utf8');\n const rows: unknown[] = [];\n for (const line of body.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed) rows.push(JSON.parse(trimmed));\n }\n return rows;\n }\n\n readJson(rel: string): unknown {\n // 走 resolvePath:读路径同样会嵌入现爬的 modelKey,越界读同样要拒绝。\n return JSON.parse(readFileSync(this.resolvePath(rel, false), 'utf8'));\n }\n}\n\n/**\n * 路径遏制被触发。\n *\n * 独立于 `CliError`:它是**内部不变量**被破坏,不是顾问能修的用法错误。\n * 命令层负责把它翻成一句人话 + 退出码,见 `packages/cli/src/commands/delivery-doctor.ts`。\n */\nexport class LayoutContainmentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LayoutContainmentError';\n }\n}\n","/**\n * 漂移握手四元组。\n *\n * 回答的是一个问题:**「我手上这份基线,还配得上眼前这个环境吗?」**\n *\n * 三个分量作门(`GATE_COMPONENTS`),第四个(backendVersion)只留档——实测 on-prem 恒返\n * \"unknown\",拿它作门只会得到一个恒等式。\n *\n * 移植自 `hcm_nx_core` 的 `tools/fde-kit/hcmnx_kit/fingerprint.py`(ADR-319 刀 6b)。\n * 行为基线是行为契约清单 §1.2 的 30 条 P。\n */\nimport { createHash } from 'node:crypto';\nimport type { Outcome, ReconProbe } from './probe';\n\n/** 🔴 只有这三个分量作门。backendVersion 仅记录——实测它在 on-prem 恒返 \"unknown\"。 */\nexport const GATE_COMPONENTS = [\n 'tenantMetaManifestDigest',\n 'extensionSource',\n 'modelListHash',\n] as const;\n\nexport const BLIND_SPOTS =\n '产品升级:manifest 只读租户 workspace 的 metas/ 目录,' +\n '产品升级改的是安装包内 _metas/,四元组不会变——该场景的防线只有行为探针一条。' +\n ' | 同字节数内容替换:清单只给 name/size/lastModified,' +\n '把标签「部门」改成「机构」在 UTF-8 下恰好都是 6 字节,摘要不变。' +\n '⇒ 字段名叫 ManifestDigest 而非 Fingerprint,名字必须说实话。';\n\nexport const MODELS_PATH = '/api/system/models';\nexport const MANIFEST_PATH = '/api/tenant-meta/list';\nexport const VERSION_PATH = '/api/version/backend';\nexport const META_PATH = '/api/models/{model}/meta';\n\nexport const EXTENSION_SOURCE_HEADER = 'x-hcm-extension-source';\n\n/**\n * 探针上限。取不到就取不到,**不许**为了拿到一个值把 761 个模型全试一遍——\n * 那会让 doctor 从「几秒」变成「几分钟」,而 doctor 存在的意义就是**快速**告诉顾问他站在哪儿。\n *\n * 🔴 为什么是 50 而不是 20:「20 够不够」这个问题,原工位实测第 1 个 key 就命中,\n * 但**这不能证明任何别的环境**;若某环境排序靠前的 key 恰好全是隐藏型(实测有 19 个),\n * 探针会空手而归并阻断门,而 key 21 可能就带着这个头。\n * ⇒ 正确的应对**不是**把 20 换成另一个同样证明不了的数字,而是:\n * ① 放宽到显然够用又不至于拖慢 doctor(命中即停,坏情况 50 × ~50ms ≈ 2.5s);\n * ② 把失败讲清楚(见 attempted/answered)。判据从「猜一个够用的数」换成「失败时说得出为什么」。\n */\nexport const MAX_SOURCE_PROBES = 50;\n\n/**\n * 本模块的全部前提是「打一个不认识的环境」。一个把 totalPages 报成 2**31 的\n * 服务端会让顾问的笔记本原地转圈。200 页 × 200 行 = 4 万行,远超真实 workspace。\n */\nexport const MAX_MANIFEST_PAGES = 200;\n\n/**\n * 🔴 只有**门相关**端点的取数失败才阻断。backendVersion 是明文钉死的「仅记录、不作门」\n * 分量——让它的取数失败从 incomplete: 侧门变成门,等于在任何缺 `/api/version/backend`\n * 的老构建上把门恒推成「不匹配」,而门被常态化绕过(`--force-stale`)正是要防的事。\n */\nconst GATE_SOURCES = [MODELS_PATH, MANIFEST_PATH, META_PATH];\n\nexport interface ModelEntry {\n modelKey?: string;\n type?: string;\n [k: string]: unknown;\n}\n\n/**\n * `/api/system/models` 的响应体 → **可用的**模型条目。\n *\n * 🔴 **这是唯一一道门**。原先 doctor / computeFingerprint / 布局解析三个点各写各的过滤,\n * 第三个漏了——而它跑在**最前面**,客户网关一改清单形状,recon 就在第一步崩掉,\n * 而 FDE 手上没有源码、没有安装包、也没有第二份基线可对照。\n *\n * 两种敌意形状都在这里挡掉,调用方不必(也不许)自己判:\n * ① `body` 整体不是对象——服务端**声明**返 object,但网关扒掉 envelope 直接返数组是真会发生的;\n * ② `models[]` 里混进非对象元素——部分序列化失败 / 拼接出错。\n *\n * ⚠️ **降级不是静默**:这里只负责「不崩」,「有没有掉东西」由调用方各自留痕。\n */\nexport function modelEntries(body: unknown): ModelEntry[] {\n if (!isPlainObject(body)) return [];\n const raw = (body as Record<string, unknown>).models;\n if (!Array.isArray(raw)) return [];\n return raw.filter(isPlainObject) as ModelEntry[];\n}\n\n/**\n * `/api/system/models` 的响应体 → 探针用的 modelKey 列表(去空串 + 排序)。\n *\n * 🔴 空串过滤不能省:一条缺 `modelKey` 的记录会让第一发打到 `/api/models//meta`,\n * 白烧一发**有限的**探针配额。\n * 🔴 排序 = 探针确定性:同一个环境两次 recon 必须探同一个模型,否则 `extensionSourceProbe`\n * 会无端漂移,看上去像环境变了。\n */\nexport function modelKeys(body: unknown): string[] {\n return modelEntries(body)\n .map((m) => (typeof m.modelKey === 'string' ? m.modelKey : ''))\n .filter((k) => k !== '')\n .sort();\n}\n\nexport interface ProbeResult {\n sourceKey: string;\n probeModelKey: string;\n attempted: number;\n answered: number;\n}\n\n/**\n * 探 `X-HCM-Extension-Source`。\n *\n * 🔴 **这个头只挂在 `GET /api/models/{model}/meta` 上,`/api/system/models` 上没有**。\n * 读错端点的后果不是「偶尔取不到」,是**每一个真实环境上恒为 unknown**:三个门分量残掉\n * 一个 ⇒ `compareFingerprint` 恒返 `incomplete:` ⇒ 门被 `--force-stale` 常态化绕过。\n *\n * 🔴 `answered` 不是凑数的统计量,它是「unknown 到底是哪种 unknown」的唯一判据。\n * 只返回 `(sourceKey, probeModelKey)` 时,「一个模型都没答应」与「答应了但没回这个头」\n * **都**返回空串、**都**写同一条失败条目——顾问看到 `unknown` 照样不知道该查连通/权限还是查反代。\n * **一个自己都分辨不了的分辨承诺比不承诺更糟**,它让人以为这件事已经处理了。\n */\nexport async function probeExtensionSource(\n probe: ReconProbe,\n keys: string[],\n): Promise<ProbeResult> {\n let attempted = 0;\n let answered = 0;\n for (const key of [...keys].sort().slice(0, MAX_SOURCE_PROBES)) {\n attempted += 1;\n const out = await probe.get(META_PATH.replace('{model}', key));\n // meta 404 是常态(隐藏型模型,实测有 19 个),换下一个\n if (out.error !== 'ok') continue;\n answered += 1;\n const value = out.headers[EXTENSION_SOURCE_HEADER];\n if (value) return { sourceKey: value, probeModelKey: key, attempted, answered };\n }\n return { sourceKey: '', probeModelKey: '', attempted, answered };\n}\n\n/**\n * 分页取全量租户元数据清单。page 从 0 开始,分页参数名是 `pageSize`(不是 `size`)。\n *\n * 🔴 一旦某页没取回来就把端点记进 failures:清单为空(真的没有 overlay 文件)\n * 与清单没爬到(401/网关截断)会算出**同一个** sha256(''),产物里必须有一处把这两件事分开,\n * 否则「爬过且为空」和「没爬」在磁盘上无法区分。\n */\nasync function fetchManifestRows(\n probe: ReconProbe,\n failures: string[],\n): Promise<Record<string, unknown>[]> {\n const rows: Record<string, unknown>[] = [];\n let page = 0;\n const pageSize = 200;\n for (;;) {\n const out = await probe.get(MANIFEST_PATH, { page, pageSize });\n if (out.error !== 'ok' || !isPlainObject(out.body)) {\n failures.push(MANIFEST_PATH);\n break;\n }\n const body = out.body as Record<string, unknown>;\n const chunk = body.data ?? [];\n if (!Array.isArray(chunk)) {\n failures.push(MANIFEST_PATH);\n break;\n }\n rows.push(...(chunk.filter(isPlainObject) as Record<string, unknown>[]));\n const totalPages = typeof body.totalPages === 'number' ? body.totalPages : 0;\n page += 1;\n if (page >= totalPages || chunk.length === 0) break;\n if (page >= MAX_MANIFEST_PAGES) {\n // 🔴 截断必须留痕,否则又是一个「截断了但看不出来」:少了后面几页的清单,\n // 摘要照样算得出来,看上去和完整清单没两样。\n failures.push(`${MANIFEST_PATH}#page-cap`);\n break;\n }\n }\n return rows;\n}\n\nfunction manifestDigest(rows: Record<string, unknown>[]): string {\n // 对**全量清单**求摘要:集合成员变化必然改变摘要(高水位做不到这点——\n // 删掉一个非最新的文件、或恢复一个旧时间戳文件,高水位纹丝不动)。\n const lines = rows\n .map((r) => `${r.name}|${r.size}|${r.lastModified}`)\n .sort();\n return sha256(lines.join('\\n'));\n}\n\n/**\n * 产物目录要用的 `sourceKey`——`recon` 打出去的**第一个**请求序列。\n *\n * 🔴 单独一个入口,而**不是**让命令层自己 `probe.get(MODELS_PATH)` 再就地取 key:\n * `probeExtensionSource` 此前有三个调用点,其中一个(recon 的这一跳)漏了\n * 「元素可能不是对象 / body 整体可能不是对象 / modelKey 可能是空串」这三道过滤,\n * 于是客户网关一改清单形状,`recon` 就在第一步以一条裸堆栈收场 —— 而顾问手上\n * 没有源码、没有安装包、也没有第二份基线可对照。\n * 三处各修各的会再漏第四处;收进 `modelKeys()` 这**一道门**,漏不掉。\n *\n * 🔴 探不到时回 `'unknown'` 而不是抛:探不到是**可降级**的(产物目录名带 unknown,\n * 顾问一眼看得出这份基线的层次身份没确认),而抛会让整轮爬取在第一步就没了。\n */\nexport async function computeReconSourceKey(probe: ReconProbe): Promise<string> {\n const out = await probe.get(MODELS_PATH);\n const p = await probeExtensionSource(probe, modelKeys(out.body));\n return p.sourceKey || 'unknown';\n}\n\nexport interface Fingerprint {\n tenantMetaManifestDigest: string;\n manifestEntryCount: number;\n extensionSource: string;\n extensionSourceProbe: string;\n extensionSourceProbeAttempted: number;\n extensionSourceProbeAnswered: number;\n modelListHash: string;\n modelCount: number;\n backendVersion: unknown;\n capabilities: string;\n createdAt: string;\n ttlDays: number;\n coverageNotes: {\n gateComponents: string[];\n blindSpots: string;\n fetchFailures: string[];\n };\n}\n\nexport async function computeFingerprint(\n probe: ReconProbe,\n opts: { ttlDays?: number; now?: () => Date } = {},\n): Promise<Fingerprint> {\n const ttlDays = opts.ttlDays ?? 30;\n const now = opts.now ?? (() => new Date());\n const failures: string[] = [];\n\n const modelsOut = await probe.get(MODELS_PATH);\n // 🔴 body 声明是 object,服务端完全可能返回 array/string——直接取属性会在客户现场炸。\n // 判成功一律 error === 'ok',再核类型。\n const modelsOk = modelsOut.error === 'ok' && isPlainObject(modelsOut.body);\n if (!modelsOk) failures.push(MODELS_PATH);\n\n const models = modelsOk ? modelEntries(modelsOut.body) : [];\n const keys = modelsOk ? modelKeys(modelsOut.body) : [];\n\n if (modelsOut.error === 'ok' && keys.length === 0) {\n // 🔴 请求成功但**一个可用 modelKey 都没有** ⇒ modelListHash 会算出 sha256('')\n // ——一个语法完整、语义为空的值——而 fetchFailures 若保持为空,门就拿它当\n // 「爬到了,就是这个值」放行。实测该环境有 761 个模型,**零可用 key 只可能是出了事**\n // (键名读错 / 权限 / 网关改写),不可能是「这个环境真的没有模型」。\n failures.push(`${MODELS_PATH}#empty-model-list`);\n }\n\n const p = await probeExtensionSource(probe, keys);\n let sourceKey = p.sourceKey;\n if (!sourceKey) {\n // 🔴 两种 unknown 的处置**相反**,所以标记必须不同。\n // answered === 0 ⇒ 没有任何模型答应 ⇒ 查连通/权限/模型清单是不是空的,**与反代无关**;\n // answered > 0 ⇒ 答应了但没回这个头 ⇒ 查反代是不是把 X-HCM-* 响应头剥了。\n failures.push(\n p.answered > 0\n ? `${META_PATH}#X-HCM-Extension-Source`\n : `${META_PATH}#no-meta-answered`,\n );\n sourceKey = 'unknown';\n }\n\n // 🔴 吃 type 而不只是 modelKey:一个模型从 DATA 变 JSON 时 meta 形状全变,\n // 只哈希 modelKey 的门对此完全无感。\n // 与上面探针的 keys 分开:那份**过滤过空串**(探针配额有限),\n // 这份是**全量**(哈希要如实反映清单,一条缺 modelKey 的记录本身就是漂移信号)。\n const hashLines = models.map((m) => `${m.modelKey ?? ''}|${m.type ?? ''}`).sort();\n const modelHash = sha256(hashLines.join('\\n'));\n\n const rows = await fetchManifestRows(probe, failures);\n\n const versionOut = await probe.get(VERSION_PATH);\n let backendVersion: unknown;\n if (versionOut.error === 'ok' && versionOut.body !== null && versionOut.body !== undefined) {\n backendVersion = versionOut.body;\n } else {\n failures.push(VERSION_PATH);\n backendVersion = { version: 'unavailable' };\n }\n\n return {\n tenantMetaManifestDigest: manifestDigest(rows),\n manifestEntryCount: rows.length,\n extensionSource: sourceKey,\n // 🔴 记下**是从哪个模型探到的**:让「这个值是谁给的」在产物里留痕。\n extensionSourceProbe: p.probeModelKey,\n // 🔴 没有这两个数,`extensionSource: \"unknown\"` 就是一个**不可追问的结论**。\n extensionSourceProbeAttempted: p.attempted,\n extensionSourceProbeAnswered: p.answered,\n modelListHash: modelHash,\n modelCount: models.length,\n backendVersion,\n capabilities: probe.capabilities,\n createdAt: `${now().toISOString().slice(0, 19)}Z`,\n ttlDays,\n coverageNotes: {\n gateComponents: [...GATE_COMPONENTS],\n blindSpots: BLIND_SPOTS,\n // 空数组 = 三个端点都真取到了;非空 = 这份四元组是**残的**,\n // 上面那些 0 和空摘要不代表环境真的空。\n fetchFailures: failures,\n },\n };\n}\n\n/**\n * 比对两份四元组,回**差异清单**(空 = 同源)。\n *\n * 🔴 某个分量没爬到时,它和「爬到了而且相等」在值上同形。两边都没爬到 → 三个分量全等\n * → 门放行一份根本没验过的基线。所以残缺本身也要进差异清单。\n */\nexport function compareFingerprint(\n recorded: Record<string, unknown>,\n current: Record<string, unknown>,\n): string[] {\n const diffs: string[] = GATE_COMPONENTS.filter((c) => recorded[c] !== current[c]);\n\n // capabilities 不进 GATE_COMPONENTS(它不是环境属性,是**爬法**属性),但仍然上报:\n // 两份基线若用不同 capabilities 爬出,那是两种爬法,值得在握手里说一声。\n if (recorded.capabilities !== current.capabilities) diffs.push('capabilities');\n\n for (const [fp, tag] of [\n [recorded, 'recorded'],\n [current, 'current'],\n ] as const) {\n const notes = fp.coverageNotes;\n // 🔴 **`coverageNotes` 整个缺失 ≠ 没有失败**。手改过的、或旧版产出的指纹没有这一节,\n // 此时「有没有取数失败」是**未知**,不是「否」。把未知读成否 ⇒ 放行一份从未验过\n // 残缺与否的基线——「爬过且没失败」与「根本没这个字段」不许同形。\n if (!isPlainObject(notes) || !('fetchFailures' in (notes as object))) {\n diffs.push(`incomplete:${tag}:coverageNotes-missing`);\n continue;\n }\n const raw = (notes as Record<string, unknown>).fetchFailures;\n const list = Array.isArray(raw) ? raw : [];\n // 🔴 报**是什么**残了,不只报**哪一侧**残了。只报侧别会把唯一可行动的信息丢掉:\n // 顾问拿到 `incomplete:recorded` 只知道「有问题」,不知道该查反代(剥了头)\n // 还是查网关分页(#page-cap)。侧别继续留着——只有 recorded 残 ⇒ 旧基线不可信,\n // 重跑 recon;只有 current 残 ⇒ 这次爬取本身有问题,先修环境。\n const gateFailures = [...new Set(list.map(String))]\n .filter((x) => GATE_SOURCES.some((s) => x.startsWith(s)))\n .sort();\n for (const f of gateFailures) diffs.push(`incomplete:${tag}:${f}`);\n }\n return diffs;\n}\n\nfunction sha256(s: string): string {\n return createHash('sha256').update(s, 'utf8').digest('hex');\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nexport type { Outcome, ReconProbe };\n","/**\n * 顾问到现场敲的第一条命令。\n *\n * 回答四个问题,一条锚点一个:**我连上了吗 / 我是谁 / 我有没有 configAdmin / 我站在哪一层**。\n *\n * 🔴 四条锚点**恒存在**。失败时少发一条,顾问看到的是「三条都绿」——他会以为都查过了,\n * 而实际上有一层根本没被体检到。少一条 ≠ 那一层没问题,少一条 = **那一层没被问过**。\n *\n * 移植自 `hcm_nx_core` 的 `tools/fde-kit/hcmnx_kit/doctor.py`(ADR-319 刀 6b)。\n * 行为基线是行为契约清单 §1.4 的 19 条 P。\n */\nimport {\n MANIFEST_PATH,\n MODELS_PATH,\n modelKeys,\n probeExtensionSource,\n} from './fingerprint';\nimport { toPrincipal } from '../auth/principal';\nimport type { ReconProbe } from './probe';\n\nexport type DoctorAnchor = 'connectivity' | 'identity' | 'config-admin' | 'layer';\n\n/** 四条锚点的顺序是固定的:连通 → 我是谁 → 有没有权限 → 站在哪一层。 */\nexport const DOCTOR_ANCHORS: readonly DoctorAnchor[] = [\n 'connectivity',\n 'identity',\n 'config-admin',\n 'layer',\n] as const;\n\nexport interface DoctorCheck {\n anchor: DoctorAnchor;\n ok: boolean;\n detail: string;\n}\n\nexport interface DoctorReport {\n ok: boolean;\n checks: DoctorCheck[];\n}\n\n/**\n * 登录身份。`null` 表示**本工具没有经手登录**——那与「登录了但服务端没给 user」\n * 是两种状态,处置不同,不许合并。\n */\nexport interface ReconIdentity {\n /** token 实际归属的租户(服务端登录响应里的 `user.tenantId`)。 */\n tenantId?: string;\n /** 顾问**要的**租户(命令行给的)。 */\n requestedTenantId?: string;\n username?: string;\n roles?: string[];\n}\n\nexport async function runDoctor(\n probe: ReconProbe,\n identity: ReconIdentity | null,\n): Promise<DoctorReport> {\n const checks: DoctorCheck[] = [];\n\n // ── 锚点 1:连通 ────────────────────────────────────────────────\n const modelsOut = await probe.get(MODELS_PATH);\n const connected = modelsOut.error === 'ok';\n // 🔴 body 声明是 object:服务端完全可能返回 array/string,直接取属性会在客户现场炸——\n // doctor 是第一条命令,它自己不许以裸异常收场。\n const modelsBody = isObj(modelsOut.body) ? modelsOut.body : {};\n checks.push({\n anchor: 'connectivity',\n ok: connected,\n detail: connected\n ? `模型清单可达,共 ${modelsBody.totalCount ?? 0} 个模型`\n : `不可达:${modelsOut.error}(status=${modelsOut.status ?? '?'})`,\n });\n\n // 🔴 从 **meta** 端点探。`/api/system/models` 的响应里**没有**这个头。\n // 读错端点会让 layer 锚点在**每一个**真实环境上报「未回显」,\n // 而单测因为假路由把头挂错了地方而恒绿。\n // 🔴 形状判断 / 空串过滤 / 排序,全在 `modelKeys` 这**唯一一道门**里。\n const keys = modelKeys(modelsOut.body);\n const p = await probeExtensionSource(probe, keys);\n\n // ── 锚点 2:我是谁 ──────────────────────────────────────────────\n // 🔴 `identity` 必须有**自己的**判据。把它的 ok 直接复用 connectivity 的布尔,\n // 「四条锚点」就只有三条真判据,第四条是装饰品。\n // 真判据是:**token 归属的租户 vs 顾问要的租户**。顾问敲错一个租户号,\n // 爬出来的基线**看上去完全正常**,而里面是另一个客户的 schema。\n checks.push({ anchor: 'identity', ...judgeIdentity(identity) });\n\n // ── 锚点 3:有没有 configAdmin ──────────────────────────────────\n const manifestOut = await probe.get(MANIFEST_PATH, { page: 0, pageSize: 1 });\n // 🔴 403 = 没权限(判红);total=0 = 有权限且确实是空的(判绿)。两者不许混——\n // 混判会把一个完全正常的克隆环境(带得走 workspace 绑定、带不走 workspace 文件)\n // 判成权限不足,顾问会去要一个他其实已经有的权限。\n const caOk = manifestOut.error === 'ok';\n const manifestBody = isObj(manifestOut.body) ? manifestOut.body : {};\n const total = caOk ? (manifestBody.total ?? '?') : '?';\n let caDetail: string;\n if (caOk) {\n caDetail =\n `租户 meta 清单可读,共 ${total} 条(0 条是合法状态:` +\n '克隆库带得走 workspace 绑定,带不走 workspace 文件)';\n } else if (manifestOut.status === 401 || manifestOut.status === 403) {\n // 🔴 不可读时**分开两种成因**,它们的下一步完全不同。压进同一句「不可读:{error}」,\n // 顾问只知道「不可读」,不知道该去找客户管理员要角色、还是去找网络组。\n // ⚠️ 压一起时两条 detail 仍然**字面不相等**,所以「两条不相等」这个判据抓不住它——\n // 判据必须落在**下一步指向谁**上。\n caDetail =\n `HTTP ${manifestOut.status}:当前账号**没有 configAdmin 权限**。` +\n '找客户管理员加角色;这不是网络问题';\n } else {\n caDetail =\n `不可读:${manifestOut.error}(status=${manifestOut.status ?? '?'})。` +\n '先排连通/网关,再谈权限。缺 configAdmin 时主锚点算不出来';\n }\n checks.push({ anchor: 'config-admin', ok: caOk, detail: caDetail });\n\n // ── 锚点 4:我站在哪一层 ────────────────────────────────────────\n checks.push({\n anchor: 'layer',\n ok: Boolean(p.sourceKey),\n detail: layerDetail(p, connected),\n });\n\n return { ok: checks.every((c) => c.ok), checks };\n}\n\nfunction judgeIdentity(ident: ReconIdentity | null): { ok: boolean; detail: string } {\n if (ident === null) {\n // 「本工具没有经手登录」≠「登录了但没给 user」。fail-closed:核不了就算没核过。\n return { ok: false, detail: '未经本工具登录,无法核对 token 归属的租户' };\n }\n const want = String(ident.requestedTenantId ?? '').trim();\n const got = String(ident.tenantId ?? '').trim();\n if (!want) {\n // 🔴 命令层忘填 `requestedTenantId` 的形态。落进下面的「不一致」分支会渲染成\n // 「token 归属租户 13,而你要的是 」——一句半截话,且**用一个工具接线缺陷去指控环境**:\n // 顾问会跑去查客户的租户配置,而问题在我们这边。\n return {\n ok: false,\n detail:\n '本工具没有记下「你要的租户」(命令层未填 requestedTenantId),' +\n `无法与 token 归属的租户 ${got || '(未知)'} 核对 —— ` +\n '这是工具接线问题,不是环境问题',\n };\n }\n if (!got) {\n return {\n ok: false,\n detail: `服务端登录响应里没有 user.tenantId,无法核对(你要的是 ${want})`,\n };\n }\n if (want !== got) {\n return {\n ok: false,\n detail:\n `🔴 **token 归属租户 ${got},而你要的是 ${want}** —— ` +\n '你正在爬另一个租户,先停下来。这一对身份会随身份段落进产物索引,' +\n '所以爬完之后在产物里也对得出来;但**落盘路径**上那个租户段取自你给的 ' +\n '`--tenant` —— 目录名照旧是你要的那个号。',\n };\n }\n return {\n ok: true,\n detail:\n `租户 ${got} 核对一致;登录身份 ${ident.username || '(未知)'},` +\n `角色 ${JSON.stringify(ident.roles ?? [])}`,\n };\n}\n\nfunction layerDetail(\n p: { sourceKey: string; probeModelKey: string; attempted: number; answered: number },\n connected: boolean,\n): string {\n if (p.sourceKey) {\n // 🔴 `probeModelKey` 不许算出来又不用:它答的是「在哪个模型上探到的」——\n // 顾问复现时要拿它去打同一个端点。\n return (\n `你站在 \\`${p.sourceKey}\\` 这一层(探自 \\`${p.probeModelKey}\\` 的 meta)。` +\n '不同 sourceKey 的 recon 产物不可互换 —— 两个 FDE 同一条命令会得到两份不同的事实,' +\n '那不是陈旧,是站在不同的层上'\n );\n }\n // 🔴 **几种** unknown 给**不同的下一步**。一句笼统的「无法确定所在层」\n // 会让顾问在一台陌生机器上无从下手。\n if (p.attempted === 0) {\n // 🔴 `attempted === 0` 必须单独说:没有 key 可试时渲染成「试了 0 个模型,一个都没答应」\n // 是把**没试**说成**试了没人应**。而 attempted === 0 本身还有两种成因,说法也不能合:\n // 清单**没取到**(去看 connectivity)vs 清单取到了但**一个可用 modelKey 都没有**\n // (connectivity 是绿的,把人支过去是浪费)。\n return connected\n ? '模型清单取到了,但里面**一个可用 modelKey 都没有**,' +\n '**一个模型都没得试** ⇒ 这不是层的问题:查清单的 modelKey 键名是不是被网关' +\n '改写、或当前账号看不到任何模型'\n : '模型清单没取到,**一个模型都没得试** ⇒ 先看上面 connectivity 那条';\n }\n if (p.answered > 0) {\n return (\n `试了 ${p.attempted} 个模型的 meta,${p.answered} 个答应了,` +\n '但都没回显 X-HCM-Extension-Source ⇒ 先查反代是不是把 X-HCM-* 响应头剥了'\n );\n }\n return (\n `试了 ${p.attempted} 个模型的 meta,**一个都没答应** ⇒ 先查连通与权限,` +\n '再看模型清单是不是空的。⚠️ 本工具只知道「一个都没答应」,' +\n '**分不出**是连通、权限还是中间有人挡了 —— 上一档(答应了但没回显 X-HCM-* 头)' +\n '才指得动反代,这一档指不动任何一方'\n );\n}\n\nfunction isObj(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/** `GET /api/auth/info` —— 服务端认定的 token 归属从这里来。 */\nexport const AUTH_INFO_PATH = '/api/auth/info';\n\n/**\n * 现打一次 `/api/auth/info`,拼出 identity 锚点要的那一对身份。\n *\n * 🔴 **复用 `toPrincipal`,不另写解析**:那个 helper 已经处理了两个来源\n * (`POST /api/auth/login` 的 `user` 与 `GET /api/auth/info` 本体)字段名一致但容器\n * 不同、以及 tenantId 可能是 number 也可能是 UUID 这两件事。再写一份就是第二套口径。\n *\n * 🔴 取不到时回 `null` 而不是空对象:**核不了 ≠ 核过了没问题**。\n * 空对象会落进「服务端没给 user.tenantId」那一档,把一次「问都没问成」\n * 说成「问了,服务端没答」——两者的下一步不同。\n */\nexport async function fetchReconIdentity(\n probe: ReconProbe,\n requestedTenantId: string,\n): Promise<ReconIdentity | null> {\n const out = await probe.get(AUTH_INFO_PATH);\n if (out.error !== 'ok') return null;\n const principal = toPrincipal(out.body);\n if (!principal) return { requestedTenantId };\n return {\n requestedTenantId,\n tenantId: principal.tenantId,\n username: principal.username,\n roles: principal.roles,\n };\n}\n","/**\n * 侦察层对 HTTP 的**窄端口**与**响应分类**。\n *\n * 🔴 为什么是端口而不是搬一份客户端过来(ADR-319 刀 6b 明令):\n * CLI 已经有认证 / endpoint 解析 / token 刷新 / 拦截器这一整条管道\n * (`packages/core/src/client.ts` + `src/http/`)。侦察层再造一份,就是\n * 「集成 reference path 必复用 helper」那条铁律的反面。\n *\n * 但**响应分类**不在那条管道里,而它是侦察层的命根子:顾问打的是一个陌生环境,\n * 「没权限」「没这个端点」「网关吐了 HTML」「连不上」的下一步完全不同。\n * 把它们压成一个布尔,产物里就再也分不出来了。所以这一层必须自己有。\n *\n * ⚠️ `error` 用字符串而不是布尔:要区分的不是「成没成」,而是「没成是哪一种」。\n * 判成功一律写 `error === 'ok'`,别写 `!error`。\n */\nimport type { AxiosInstance } from 'axios';\n\n/** 响应分类的完整取值域。 */\nexport type OutcomeError =\n | 'ok'\n | 'unauthorized'\n | 'forbidden'\n | 'not-found'\n | 'server-error'\n | 'bad-response'\n | 'network';\n\nexport interface Outcome {\n /** 网络层没走通时是 `-1`——它不是 HTTP 状态,别拿去和 4xx 比。 */\n status: number;\n /** 🔴 是 unknown 不是对象:服务端完全可能返回 array / string / number。 */\n body: unknown;\n error: OutcomeError;\n /**\n * 本次响应的头,键一律小写。\n *\n * 🔴 挂在 Outcome 上而不是客户端实例上(Python 侧是 `client.last_headers`):\n * 那种可变字段有「串味」风险——上一次成功的头留到这一次失败时被读到,\n * 而 `probeExtensionSource` 恰恰是紧跟每次 GET 读头。挂在返回值上让串味**结构上不可能**,\n * 契约里那几条「last_headers 必须清空」于是从「要测的行为」变成「构造上成立」。\n */\n headers: Record<string, string>;\n}\n\nexport interface ReconProbe {\n get(path: string, params?: Record<string, unknown>): Promise<Outcome>;\n post?(path: string, body: unknown): Promise<Outcome>;\n getMany?(paths: string[]): Promise<Record<string, Outcome>>;\n postMany?(jobs: Array<{ path: string; body: unknown }>): Promise<Record<string, Outcome>>;\n /** 爬取时声明的 capability 串——它是**爬法**属性,不是环境属性。 */\n readonly capabilities: string;\n}\n\nconst STATUS_CLASS: Record<number, OutcomeError> = {\n 401: 'unauthorized',\n 403: 'forbidden',\n 404: 'not-found',\n};\n\n/**\n * 状态码 → 分类。\n *\n * 🔴 整个 2xx 段都是成功。只认 200 会害得 201/202/206 被归成 `bad-response`——\n * 一个正常的「已创建 / 处理中 / 分段内容」响应被记成失败。\n */\nexport function classifyStatus(status: number): OutcomeError {\n if (status >= 200 && status < 300) return 'ok';\n if (STATUS_CLASS[status]) return STATUS_CLASS[status];\n if (status >= 500) return 'server-error';\n return 'bad-response';\n}\n\n/** 能力头:声明本次爬取用的 capability 串。 */\nexport const CAPABILITIES_HEADER = 'x-hcm-capabilities';\n\n/**\n * 把 `params` urlencode 到 URL 上。\n *\n * 🔴 三个坑各栽过一次:中文必须 urlencode;path 已带 query 时要用 `&` 续接\n * 而不是再来一个 `?`;base 末尾的 `/` 不能和 path 前导的 `/` 拼成 `//`。\n */\nexport function buildUrl(path: string, params?: Record<string, unknown>): string {\n const entries = Object.entries(params ?? {}).filter(([, v]) => v !== undefined && v !== null);\n if (entries.length === 0) return path;\n const qs = entries\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join('&');\n return `${path}${path.includes('?') ? '&' : '?'}${qs}`;\n}\n\nfunction lowerHeaders(raw: unknown): Record<string, string> {\n const out: Record<string, string> = {};\n if (raw && typeof raw === 'object') {\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\n out[k.toLowerCase()] = String(v);\n }\n }\n return out;\n}\n\n/**\n * 把一次 axios 调用的结果归类成 `Outcome`。\n *\n * 🔴 空体的两档不能合:\n * · 2xx 且空体 ⇒ `bad-response`。静默把空当成结果,正是最怕的丢数据形态。\n * 例外是 204 No Content——它**声明**了没有内容,是 `ok` + `body=null`。\n * · 非 2xx 且空体 ⇒ **保留状态分类**,不降级成 `bad-response`。\n * 500 空体仍是 `server-error`;降级会让顾问去查自己请求写错没有,而真相在服务端。\n *\n * 🔴 非 2xx 但**有** body 时 body 要留着:实测反代对大 DSL 查询吐 `504 + HTML 网关超时页`,\n * 顾问拿到 `bad-response` 会去查自己 DSL 键写错没有,而真相是查询超时。\n * 但 HTML 这种非 JSON 体留着也没用且会污染产物 ⇒ 保留分类、body 置空。\n */\nfunction toOutcome(status: number, data: unknown, headers: Record<string, string>): Outcome {\n const cls = classifyStatus(status);\n if (status === 204) return { status, body: null, error: 'ok', headers };\n const empty =\n data === undefined ||\n data === null ||\n (typeof data === 'string' && data.trim() === '');\n if (empty) {\n return { status, body: null, error: cls === 'ok' ? 'bad-response' : cls, headers };\n }\n if (typeof data === 'string') {\n // 字符串体:JSON 解析得出来就当结构化体,解析不出(HTML 错误页等)就置空但保留分类。\n try {\n return { status, body: JSON.parse(data), error: cls, headers };\n } catch {\n return { status, body: null, error: cls === 'ok' ? 'bad-response' : cls, headers };\n }\n }\n return { status, body: data, error: cls, headers };\n}\n\n/** 把既有 axios 管道(已带认证与刷新)适配成侦察层的窄端口。 */\nexport function httpProbe(http: AxiosInstance, capabilities: string): Required<ReconProbe> {\n async function once(\n fn: () => Promise<{ status: number; data: unknown; headers?: unknown }>,\n ): Promise<Outcome> {\n try {\n const res = await fn();\n return toOutcome(res.status, res.data, lowerHeaders(res.headers));\n } catch (e) {\n // 🔴 **本层自己的 bug 不许伪装成 network**:`TypeError` / `ReferenceError` 这类\n // 是编程错误,接住它等于把一条本该看得见的堆栈变成一句「网络不通」,\n // 顾问会去查 VPN,而真相在我们的代码里。单条路径**照抛**。\n // ⚠️ 只有**批量**路径才把它兜成 network(一路炸不许拖垮整批),\n // 这个不对称是刻意的——批量那边的兜底会在产物 note 里写明「去看堆栈」。\n // 🔴 URL 本身非法之类的**本地**错误单独落 bad-response:归进 network\n // 会让顾问白查一遍 VPN。\n const msg = e instanceof Error ? e.message : String(e);\n const local = /Invalid URL|ERR_INVALID_URL|ERR_UNESCAPED_CHARACTERS/i.test(msg);\n // ⚠️ **顺序不能倒过来**:Node 的「Invalid URL」本身就是一个 `TypeError`\n // (`TypeError [ERR_INVALID_URL]`),先判编程错误会把一个**已分类**的畸形 URL\n // 抛成堆栈。实测栽过一次。畸形 URL 是输入问题,不是我们写错了。\n if (!local && isProgrammingError(e)) throw e;\n // TLS / 证书失败同样归 network:它确实「没收到 HTTP 响应」。\n // note 侧负责说清「配 CA 或改 http,查链路是白查」。\n return { status: -1, body: null, error: local ? 'bad-response' : 'network', headers: {} };\n }\n }\n\n const headers = { [CAPABILITIES_HEADER]: capabilities };\n\n const probe: Required<ReconProbe> = {\n capabilities,\n get(path, params) {\n return once(() =>\n http.get(buildUrl(path, params), { headers, validateStatus: () => true }),\n );\n },\n post(path, body) {\n return once(() =>\n http.post(path, body, {\n headers: { ...headers, 'content-type': 'application/json' },\n validateStatus: () => true,\n }),\n );\n },\n /**\n * 🔴 **按原始路径回填、输入全覆盖**:调用方拿 `out[path]` 取结果,\n * 少一个键就会读成 `undefined` 然后当成「没这个东西」。\n * 🔴 一路炸不许拖垮整批——逐路径独立归类。\n */\n async getMany(paths) {\n const pairs = await Promise.all(\n paths.map(async (p): Promise<[string, Outcome]> => [\n p,\n await probe.get(p).catch(() => networkOutcome()),\n ]),\n );\n return Object.fromEntries(pairs);\n },\n async postMany(jobs) {\n const pairs = await Promise.all(\n jobs.map(async (j): Promise<[string, Outcome]> => [\n j.path,\n await probe.post(j.path, j.body).catch(() => networkOutcome()),\n ]),\n );\n return Object.fromEntries(pairs);\n },\n };\n return probe;\n}\n\n/**\n * 是不是**我们自己写错了**(而非传输失败)。\n *\n * axios 的传输错误带 `isAxiosError`,先认它;剩下的 `TypeError` / `ReferenceError` /\n * `SyntaxError` 一律判成编程错误。判据故意写窄:宁可把一个没见过的异常归成 network\n * (它至少落进一个 note 写明「不保证列全、去看堆栈」的桶),\n * 也不要把一个真的传输错误抛成堆栈吓退顾问。\n */\nfunction isProgrammingError(e: unknown): boolean {\n if (!(e instanceof Error)) return false;\n if ((e as { isAxiosError?: boolean }).isAxiosError) return false;\n return e instanceof TypeError || e instanceof ReferenceError || e instanceof SyntaxError;\n}\n\nfunction networkOutcome(): Outcome {\n return { status: -1, body: null, error: 'network', headers: {} };\n}\n\nexport { toOutcome as classifyResponse };\n","/**\n * 一轮 recon 的编排:爬 → 判读 → 建图 → 落盘 → 台账。\n *\n * 🔴 这一层是三份整图产物(topology / valuedomain / mapping)的**唯一**生产者。\n * 各模块单测都能被直接调通,但「接线没接上」完全不表现为报错 —— 只表现为顾问手上\n * 少了三个文件(本仓「能力在但线没接」那条高频形态)。所以这一层的守卫大半在\n * **接线**上,而不在算法上。\n */\nimport { randomBytes } from 'node:crypto';\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport * as path from 'node:path';\n\nimport { compareFingerprint, computeFingerprint, modelEntries, modelKeys } from '../fingerprint';\nimport type { ReconLayout } from '../layout';\nimport type { ReconProbe } from '../probe';\nimport { KIT_CARRY_OVER_KEY } from './carry-over';\nimport { DEFAULT_META_TYPE, crawlMeta, crawlModels, writeCrawlProducts } from './crawl';\nimport { extractFieldRows, writeFieldsIndex } from './fields';\nimport { collectActionGates, writeGates, type Gate, type GateCoverage } from './gates';\nimport { buildMapping, collectMutation, writeMapping } from './mapping';\nimport { renderModelsMd, writeModelsMd } from './modelsmd';\nimport { auditValueDomainPointers } from './pointer-audit';\nimport { refEdges } from './refs';\nimport { buildTopology, writeTopology } from './topology';\nimport {\n COMPLETENESS,\n collectCodeTables,\n collectEnumDomains,\n unsafeTableKeyReason,\n writeValuedomain,\n} from './valuedomain';\n\n/**\n * 🔴 常量本体在 `carry-over.ts`:写这一节的**不止 runner** —— `mapping/_index.json`\n * 与 `valuedomain/_index.json` 由它们各自的模块写,而那两个模块不许 import runner\n * (方向纪律)。这行 re-export 是给命令层与用例用的单一入口。\n */\nexport { KIT_CARRY_OVER_KEY };\n\n/**\n * `--model X` 在模型清单里一个都没匹配上。\n *\n * 🔴 **必须在写盘之前抛**。原先它一路走完编排:`fields.jsonl` 被清成 0 字节、\n * `gates.json` 清空,而摘要是 `rc=0 / modelsQueried:0 / driftSinceLastRecon: []`\n * ——读作「跑成功了,环境没变」。此后 `find` 任何词都印「0 命中 / 索引共 0 字段」,\n * 与「产品没有这个字段」同形(GC3 要分开的两件事),方向是**低报**。\n *\n * 代价在客户现场是最重的一档:FDE 手上**只有这一份基线**,没有源码、没有安装包可以重来,\n * 刚跑完的一次全量(可能几十分钟)会被一个拼错的模型名抹掉,而且没有任何迹象告诉他出事了。\n */\nexport class TargetModelNotFound extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TargetModelNotFound';\n }\n}\n\n// 🔴 这一节回答的是「这份产物里,哪些是这一轮真爬的、哪些是沿用上一轮的」——\n// 少了它,定向重爬产出的基线与全量基线在磁盘上**逐字同形**。\nconst CARRY_NOTE =\n 'mode=targeted 时本轮只重爬了 modelsRecrawled 里的模型;modelsCarriedOver 的字段与只读闸' +\n '是从上一轮落盘的产物里**读回来沿用**的,不是这一轮观测到的。' +\n 'coverage 的其余统计量(modelsQueried / modelsAnswered / unavailable / rejected …)' +\n '只描述**本轮**,不含沿用部分 —— 别拿它们去解释 gates 的总条数。' +\n 'mode=full 时两个列表都为空,这是「全量爬过」而不是「没爬」。' +\n // 🔴 三份新产物在定向重爬下的处置,必须写死在产物里 —— 它们都是**整图/整册**口径的\n // 产物,读者最容易的误读恰恰是「只重爬一个模型 ⇒ 这三份只覆盖那一个模型」。\n ' | topology / valuedomain / mapping 三份产物在 mode=targeted 时**照旧整体重算**,' +\n '但重算用的输入分两半,别混着读:' +\n '① **现打服务端**的那半(码表 rows、mutation-capability)覆盖全部模型,' +\n '**全部是本轮观测**;' +\n '② **吃 meta** 的那半(topology 的边集、mapping 的字段清单、valuedomain/_enums.jsonl)' +\n '对 modelsCarriedOver 里的模型用的是**沿用的 meta**。' +\n 'topologyRecomputed=false 说的正是 ②:这张图这一轮**算过**,只是它的边有一部分来自' +\n '上一轮观测的 meta —— **不是**「没算」。mode=full 时它为 true。';\n\n/**\n * 每个模型的爬取时刻,写进**内容**而不是靠文件 mtime。\n *\n * 🔴 理由是判据本身:**说错年龄比不说更坏**。mtime 会被 `cp`(不带 `-p`)、打包解包、\n * rsync 改写 —— 复评实测 `cp -r` 之后「最早」由 2026-08-13 变 2026-08-16,\n * 沿用行再次被报成刚爬的。而 FDE 的产物被拷来拷去(发同事 / 打包带走 / rsync 上跳板机 /\n * 装进交付包)**是主路径不是边缘场景**,光加一句免责声明等于让他继续读一个错日期。\n *\n * 🔴 落点是**独立文件**,不许往 `meta/<key>.model.json` 里塞键:那些是原样留证的产品\n * 响应体,污染它们就毁掉了「这是服务端原话」这个唯一价值。文件名带 `_kit` 前缀 ——\n * 一眼看得出是工具写的,产品从不发它。\n */\nexport const KIT_CRAWLED_AT_FILE = '_kitCrawledAt.json';\n\nconst CRAWLED_AT_NOTE =\n 'models[<模型>] = 该模型的 meta **最后一次真正被爬下来**的时刻(UTC)。' +\n '定向重爬只更新本轮重爬的那些,沿用的保持原值不动 —— 这份文件正是「这份基线有多老」' +\n '的唯一可靠依据:文件 mtime 会被 cp/打包/rsync 改写,它不会。' +\n '值为 null = 这个模型是沿用来的、而产出它的那一轮**没有记录时间戳**' +\n '(基线由更早版本的 kit 产出)—— 与「刚爬的」是两件事,不许合并。';\n\n/**\n * 「带轮次戳的产物」名单是**判据的单一来源**:`runRecon` 按它写、`inspectRound` 按它读。\n *\n * 🔴 两边各写一份清单,下一个新产物必然只补一边 —— 而漏补的表现是\n * 「那份产物永远被判成本轮的」,一个只在中断时才暴露的静默缺陷。\n */\nexport const ROUND_STAMPED_PRODUCTS = [\n 'topology.json',\n 'valuedomain/_index.json',\n 'mapping/_index.json',\n] as const;\n\n/**\n * 🔴 三档**不许合并**,它们的下一步动作完全不同:\n * complete = 所有产物同属一轮,且这一轮跑到了最后(台账落了盘);\n * mixed = 存在不同轮次的产物 / 有产物没戳 / 有产物缺席 / 这一轮没跑完 —— 都要指名道姓;\n * unknown = 产物里根本没有 roundId(更早版本的 kit 爬的),**判不了**。\n * ⚠️ `unknown` 这一档不许省:老基线不带戳,把它判成 complete 或 mixed 都是撒谎。\n */\nexport const ROUND_STATES = ['complete', 'mixed', 'unknown'] as const;\nexport type RoundState = (typeof ROUND_STATES)[number];\n\n/**\n * 档位 → 退出码。**这张表是唯一一处做这个映射的地方**,命令层只查表、一个字都不重判。\n *\n * 🔴 `unknown` 也非零:它是「判不了」,而脚本把「判不了」当成「没问题」继续往下走,\n * 正是这一整节要防的事。\n */\nexport const ROUND_EXIT_CODES: Record<RoundState, number> = { complete: 0, mixed: 1, unknown: 2 };\n\n/**\n * grep 的头**只写一处**:`ROUND_NOTE`(产物里的说明)与摘要的 `recheckWith`\n * (跑完那一刻的提示)抄两份必然漂移,而漂移的表现是「产物教的读法与摘要教的不一样」。\n *\n * ⚠️ 用 `-e … -e …` 而不是 `\\|` 交替:后者是 GNU BRE 扩展,BSD grep(客户可能是 macOS /\n * 精简版 busybox)上未必认 —— 一条**在部分机器上敲不通**的命令与敲不通没有区别。\n * 两个模式缺一不可:`roundId` 判「是不是同一轮」,`productsWrittenThisRound` 判\n * 「这一轮跑完了没有」,只 grep 前者会把「对齐」读成「没问题」。\n */\nconst GREP_RECIPE_HEAD = `grep -H -e '\"roundId\"' -e '\"productsWrittenThisRound\"'`;\n\n/** 零网络子命令的名字。产物里教的读法与摘要里给的提示都从这里取,不抄第二份。 */\nexport const ROUND_SUBCOMMAND = 'hcm delivery recon round';\n\nconst ROUND_NOTE =\n 'roundId = 这一轮 recon 的身份(起跑时刻 + 随机后缀,可排序、同秒两轮也不会撞)。' +\n 'productsWrittenThisRound = 本轮**真正落盘**的产物相对路径 —— 判据是文件真的写出来了,' +\n '不是「本轮打算写」;`null` 表示这一轮没跑到写这份台账那一步(中断 / 断网 / 进程被杀),' +\n '与 `[]`(跑到了、一份都没写)是两件事。' +\n 'topology.json / valuedomain/_index.json / mapping/_index.json 各自带一份同名的 roundId:' +\n '它们与这里对不上,就说明那几份产物是**别的轮次**留下的 —— ' +\n '此时 `mapping/` 里「没有某个模型」可能只是本轮没写到那里,不是产品的结论。' +\n ' | 怎么当场判(零网络):' +\n `① 肉眼对账,**在产物目录里**执行 \\`${GREP_RECIPE_HEAD} ${KIT_CRAWLED_AT_FILE} ` +\n `${ROUND_STAMPED_PRODUCTS.join(' ')}\\`。` +\n // 🔴 三种读法缺一不可:漏掉中间那一档时,顾问会把「四行对齐」直接读成「没问题」——\n // 而真机第 14.4s 中断产出的正是「四行全对齐、轮次却没跑完」。\n '读法有三种:**四个 roundId 对不齐** = 混装(那几份产物是别的轮次留下的);' +\n '**四个都对齐、但 productsWrittenThisRound 是 `null`** = 这一轮**没跑完**' +\n '(写完最后一份产物之前就断了,真机实测存在这一档,别把「对齐」读成「没问题」);' +\n '**一行都 grep 不到** = 这份基线是更早版本的 kit 爬的,判不了。' +\n // 🔴 ② 教的命令必须**今天真能敲**:指一条敲不通的命令比不指更坏。\n // 参数是**产物目录本身**(就是你现在站的这个目录),不是产物根 ——\n // 少一层「在同一租户下选哪一份基线」的推断,就少一处会错的地方。\n '② 让工具说人话(complete / mixed / unknown 三档 + 指名道姓说出哪份产物旧到哪一轮):' +\n `在产物目录里执行 \\`${ROUND_SUBCOMMAND} --dir . --output table\\`;` +\n '退出码 0=complete / 1=mixed / 2=unknown,写进脚本当前置闸也可以。' +\n '⚠️ 它要 `@hcmai/cli` 装过(`npx @hcmai/cli` 亦可);装不了就走 ①,' +\n 'grep 是任何机器上都有的那条路。' +\n '判据本体是 `@hcmai/sdk` 的 `inspectRound()` / `renderRound()`,只读文件、零网络,' +\n '产物被单独拷走后照样直接调得动。';\n\n/**\n * 同一句话必须**在 topology.json 自己身上**再说一遍:产物是会被**单独拷走**的文件\n * (发同事 / 打包带走 / rsync 上跳板机),口径只写进 gates.json 的 `_kitCarryOver` 与\n * CLI 摘要,拿走 topology.json 的人就会把整张图当成这一轮观测到的。\n */\nconst TOPOLOGY_RECOMPUTE_NOTE =\n 'recomputedThisRound=true:本轮是全量爬取,图里每一条边都算自**这一轮**取回的 meta。' +\n 'recomputedThisRound=false:本轮是定向重爬(--model),这张图**照样整体重算了** —— ' +\n '节点取自本轮的模型名册,边集也整体重算 —— 但 modelsRecrawledThisRound 之外的模型,' +\n '它们的边来自**上一轮落盘的 meta**。所以 false 的意思是「算过,部分输入是旧的」,' +\n '**不是**「这一轮没算」。沿用了哪些模型、上一轮是什么时候爬的:' +\n // 🔴 指路必须**指到那一层**:这一节挂在 gates.json 的 `coverage` 下,不在顶层。\n // 少写一层,顾问在顶层 grep 不到就会以为这份基线根本没记沿用情况 ——\n // 与「指一个不存在的文件」同一类错。\n `见 gates.json 的 coverage.${KIT_CARRY_OVER_KEY} 与产物根的 ${KIT_CRAWLED_AT_FILE};` +\n `mapping/_index.json 与 valuedomain/_index.json 也各带一份同名的 ${KIT_CARRY_OVER_KEY}(顶层)。`;\n\n// ── 轮次身份 ──────────────────────────────────────────────────────────────────\n// 🔴 **为什么要有它**(2026-08-16 真机复现,不是推演):一轮 recon 跑到一半被 Ctrl-C,\n// 产物目录里就是**两代混装**的,而且没有任何标记。实测(localhost:8087 / 租户 13,\n// 先跑完一轮再起一轮并在第 8.0s 发 SIGINT)按 mtime 分成两代:\n// 本轮重写:models.json / meta×742 / unavailable.json / gates.json / index×2 /\n// _kitCrawledAt.json / topology.json\n// 沿用上一轮:valuedomain×74 / mapping×743 / fingerprint.json\n// 于是顾问打开 `mapping/` 看不到某个模型 —— 这与「那个模型的 modelKey 不安全、被拒写\n// 并留了痕」**完全同形**,而后者是产品的正常输出。GC3 的老形态:「本轮没写」与\n// 「本轮写了、结论是没有」被塞进了同一个观测。\n//\n// 🔴 roundId 的形态:**起跑时刻(可排序)+ 随机后缀(可区分)**,两半缺一不可。\n// · 只有时刻 ⇒ 同一秒起跑的两轮拿到同一个 id ⇒ 混装被判成「同一轮」;\n// · 只有随机 ⇒ 顾问拿到两个 id 说不出哪个更早,而 `mixed` 的文案要求\n// **指名道姓说出旧到哪一轮** —— 说不出先后就等于没说。\n// ⚠️ 随机那半用 `crypto.randomBytes`(不是 `Math.random`):这里要的恰恰是\n// 「不可能与另一轮撞上」,而 `Math.random` 在同一进程里是可预测的伪随机序列。\n\nfunction two(n: number): string {\n return String(n).padStart(2, '0');\n}\n\n/** `20260816T075429Z` —— roundId 的前半,给顺序。 */\nfunction roundStamp(d: Date): string {\n return (\n `${d.getUTCFullYear()}${two(d.getUTCMonth() + 1)}${two(d.getUTCDate())}` +\n `T${two(d.getUTCHours())}${two(d.getUTCMinutes())}${two(d.getUTCSeconds())}Z`\n );\n}\n\n/** `2026-08-16T07:54:29Z` —— 台账里的人读时刻。 */\nfunction isoStamp(d: Date): string {\n return d.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/** roundId 前半 → Date;形状不对就回 null(**宁可少说一句,也不许说错一个时间**)。 */\nfunction parseRoundStamp(stamp: string): Date | null {\n const m = /^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z$/.exec(stamp);\n if (!m) return null;\n const [, y, mo, d, h, mi, s] = m as unknown as string[];\n return new Date(Date.UTC(+y!, +mo! - 1, +d!, +h!, +mi!, +s!));\n}\n\n/** `20260816T075429Z-a1b2c3d4`。前半给顺序,后半给身份(理由见上方注释)。 */\nexport function newRoundId(started: Date): string {\n return `${roundStamp(started)}-${randomBytes(4).toString('hex')}`;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\n\n/**\n * 读一份 JSON 产物,**只**把「文件不在」与「不是合法 JSON」兜成 `undefined`。\n *\n * 🔴 别的异常照旧往上抛:越界闸(`LayoutContainmentError`)、权限、IO 是系统性故障,\n * 吞掉它们会让一份处处是窟窿的基线看起来正常。口径与 `valuedomain` / `mapping`\n * 的落盘 catch 一致。\n */\nfunction readJsonSoft(file: string): unknown {\n let text: string;\n try {\n text = readFileSync(file, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return undefined;\n throw err;\n }\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\nfunction readCrawledAt(layout: ReconLayout): Record<string, unknown> {\n const body = readJsonSoft(path.join(layout.dir, KIT_CRAWLED_AT_FILE));\n const models = isRecord(body) ? body.models : null;\n return isRecord(models) ? models : {};\n}\n\n/**\n * 新爬的记当前时刻,沿用的原值不动(拿不到原值就显式记 `null`)。\n *\n * 🔴 每个模型的时刻用**本轮起跑时刻**(`started`),不是「写这一行的那一刻」:\n * 同一轮里逐个取 now 会让 742 行分布在十几秒里,于是「这一行是哪一轮爬的」只能靠猜。\n * 用起跑时刻,它与 `roundStartedAt` 逐字相等,两者可互相印证。\n * 误差上界就是一轮的时长(本机实测 15.16s),远小于这份数据的用途(判基线多老)。\n */\nfunction writeCrawledAt(\n layout: ReconLayout,\n freshKeys: string[],\n carriedKeys: string[],\n roundId: string,\n started: Date,\n): Record<string, string | null> {\n const previous = readCrawledAt(layout);\n const now = isoStamp(started);\n const models: Record<string, string | null> = {};\n for (const key of carriedKeys) {\n // 🔴 `previous[key]` 可能是 undefined/null —— 那是「沿用了但不知道它多老」,\n // 与「刚爬的」是两个状态(GC3),落成显式 null 而不是悄悄补上 now。\n const prev = previous[key];\n models[key] = typeof prev === 'string' ? prev : null;\n }\n for (const key of freshKeys) models[key] = now;\n const sorted: Record<string, string | null> = {};\n for (const key of Object.keys(models).sort()) sorted[key] = models[key] ?? null;\n layout.writeJson(KIT_CRAWLED_AT_FILE, {\n note: CRAWLED_AT_NOTE,\n roundNote: ROUND_NOTE,\n roundId,\n roundStartedAt: now,\n // 🔴 这一刻**必然**是 null:本轮还没写完,台账要等到最后一份产物落盘才知道。\n // 恒发键(GC3):`null` = 没跑到那一步,`[]`/清单 = 跑到了。\n // ⚠️ 正因为每一轮起手都会把它重置成 null,它才能当「这一轮跑完了没有」的见证 ——\n // 上一轮留下的清单不会冒充本轮的。\n productsWrittenThisRound: null,\n models: sorted,\n });\n return sorted;\n}\n\n/**\n * 一轮的**最后一笔**:把本轮真正落盘的产物清单补进 `_kitCrawledAt.json`。\n *\n * 🔴 为什么要**重写一次**这个文件而不是新开一份:`writeCrawledAt` 必须早写\n * (它要在 `carryOver` 读完旧值之后、meta 落盘那一段之前定下轮次身份),\n * 而台账只有跑到最后才知道内容。两次写同一份文件的代价是一次小 IO,\n * 换来的是「这份文件本身就是本轮跑完的见证」——中断在最后一份产物之后、\n * 台账之前时,三份索引的 roundId 全都对得上,此时唯一还能说话的就是它。\n * (真机第 14.4s 那次中断正是这个形态:只有 fingerprint.json 是上一轮的。)\n *\n * 🔴 判据是**文件真的写出来了**,不是「本轮打算写」——口径与 `codeTableFiles` 那段一致。\n * 承重的是**推导**那一半:`products` 里的 meta 文件先按 `rejectedMetas` 剔过\n *(把它换成 `Object.keys(metas)` 会红),`vdWritten` / `mappingPaths` 也都是写入方\n * 自己回报的「真写出去的那些」。\n *\n * ⚠️ 下面这道 `existsSync` 是**第二条独立证据链**,而它在当前写入方组合下\n * **一次都走不到** —— 这是**实测**的,不是推的:把过滤换成「排掉任何一个就抛」跑全套用例,\n * 探针零命中。⇒ 把它删掉**不会有任何用例红**(变异实测确认)。\n * 留着的理由不是「它抓到过什么」,而是:上面那条推导**自己也可能错,而错法恰好是不报错的**\n * (产物里凭空多一行台账,与真写出去了同形)—— 这与 `danglingValueDomainPointers` 那对\n * 双链是同一条纪律。代价是每份产物一次 stat(真机 1567 条约 5–7ms,整轮的 0.04%)。\n * 🔴 别为它编一条「测得出差别」的用例:写入方不变就造不出那个差别,硬造出来的只会是\n * 一条测夹具的用例。\n */\nfunction finishRound(\n layout: ReconLayout,\n models: Record<string, string | null>,\n roundId: string,\n started: Date,\n products: string[],\n): string[] {\n const written = [...new Set(products.filter((rel) => rel && existsSync(path.join(layout.dir, rel))))].sort();\n layout.writeJson(KIT_CRAWLED_AT_FILE, {\n note: CRAWLED_AT_NOTE,\n roundNote: ROUND_NOTE,\n roundId,\n roundStartedAt: isoStamp(started),\n productsWrittenThisRound: written,\n models,\n });\n return written;\n}\n\n/** 产物绝对路径 → 相对产物根的 posix 路径。写盘用的 rel 就长这样,两端才对得上。 */\nfunction relOf(layout: ReconLayout, p: string): string {\n const rel = path.relative(layout.dir, p);\n // `..` 开头说明它压根不在产物根下 —— 那是编排配置错,原样交出去比拼一个假 rel 强。\n if (!rel || rel.startsWith('..')) return p;\n return rel.split(path.sep).join('/');\n}\n\n/**\n * → `[读出来的状态, roundId]`。状态取自**产物本身**,不做任何推断。\n *\n * 🔴 「没有戳」必须是它自己的一档,不能与「戳着别的轮次」合并 ——\n * 判据「A 与 B 的 roundId 不同」的前提是**两边都有 roundId**,前提不成立时\n * `null !== \"…\"` 会碰巧得出正确结论而理由是错的,反过来两边都没戳时\n * `null === null` 会得出「同一轮 ⇒ complete」,那就是撒谎。\n */\nfunction productRoundId(root: string, rel: string): [string, string | null] {\n const p = path.join(root, rel);\n if (!existsSync(p)) return ['missing', null];\n const body = readJsonSoft(p);\n if (body === undefined || !isRecord(body)) return ['unreadable', null];\n const rid = body.roundId;\n if (typeof rid !== 'string' || !rid) return ['unstamped', null];\n return ['stamped', rid];\n}\n\nexport interface RoundProduct {\n path: string;\n state: 'this-round' | 'older-round' | 'unstamped' | 'missing' | 'unreadable';\n roundId: string | null;\n}\n\nexport interface LedgerFilesMissing {\n checked: number;\n count: number;\n files: string[];\n}\n\nexport interface RoundResult {\n state: RoundState;\n roundId: string | null;\n roundStartedAt: unknown;\n finished: boolean | null;\n productsWrittenThisRound: number | null;\n ledgerFilesMissing: LedgerFilesMissing | null;\n products: RoundProduct[];\n dir: string;\n note: string;\n}\n\n/**\n * 读一份**已经落盘**的基线,回答「它是不是一轮完整跑出来的」。\n *\n * 只读文件、零网络:顾问在气隙机器上、或者刚被 Ctrl-C 打断之后,手上就只有这几个文件\n * (被打断的那一轮**不会**打印任何摘要 —— 所以这个判断不能只活在摘要里)。\n */\nexport function inspectRound(root: string): RoundResult {\n const raw = readJsonSoft(path.join(root, KIT_CRAWLED_AT_FILE));\n const ledger: Record<string, unknown> = isRecord(raw) ? raw : {};\n const rid = ledger.roundId;\n const roundId = typeof rid === 'string' && rid ? rid : null;\n const rawWritten = ledger.productsWrittenThisRound;\n const written = Array.isArray(rawWritten) ? rawWritten : null;\n\n // 🔴 台账点了名的产物,**现在还在不在盘上**。\n // 与中断那一档同形的另一张脸:中断留下混装基线而工具主动盖 `complete` 的章;\n // 这里是**拷贝 / 裁剪**留下残缺基线而工具同样盖章 —— 而后者概率高得多:\n // 前者要撞 0.14s 的窗口,本档只要顾问 scp / U 盘 / 压缩包漏拷一次,或为脱敏删几份。\n // 「FDE 的产物被拷来拷去是主路径」是本 kit 在时间戳那一轮就立下的判断。\n //\n // 🔴 **全查,不抽查**(先量了才定的):真机 1567 条台账全 stat 三次分别\n // 6.68 / 5.54 / 5.13ms —— 整轮 recon 14.4s 的 0.04%。抽查会引入「抽了多少、怎么抽的」\n // 这一串要额外交代的口子(no silent caps),而全查根本不需要那些交代。\n // `checked` 恒发:查了几份必须说得出来。\n // 🔴 `null` = **查不了**(台账是 null:这一轮没跑到写台账那一步,或老基线没有这一节),\n // 与 `{count: 0}`(查过了、一份不缺)是两个答案,不许同形(GC3)。\n let ledgerMissing: LedgerFilesMissing | null = null;\n if (written !== null) {\n const gone = written\n .filter((rel): rel is string => typeof rel === 'string' && !existsSync(path.join(root, rel)))\n .sort();\n ledgerMissing = { checked: written.length, count: gone.length, files: gone };\n }\n\n const products: RoundProduct[] = [];\n for (const rel of ROUND_STAMPED_PRODUCTS) {\n const [readState, pid] = productRoundId(root, rel);\n const state =\n readState === 'stamped'\n ? roundId && pid === roundId\n ? 'this-round'\n : 'older-round'\n : (readState as RoundProduct['state']);\n products.push({ path: rel, state, roundId: pid });\n }\n\n let state: RoundState;\n let finished: boolean | null;\n if (roundId === null && products.every((p) => p.roundId === null)) {\n // 一个戳都没有 ⇒ 判不了。**不许**因为「三份都没有戳所以都一样」而报 complete。\n state = 'unknown';\n finished = null;\n } else {\n finished = written !== null;\n // 🔴 第三个合取项:台账点名的文件缺了一份,这份基线就不是「完整一轮」——\n // 它可能是拷贝时漏带、也可能被人删过,但无论哪种,拿它下结论都会踩到\n // 「这里没有某个模型」=「产品里真的没有」那个同形陷阱。\n state =\n roundId && finished && !ledgerMissing?.count && products.every((p) => p.state === 'this-round')\n ? 'complete'\n : 'mixed';\n }\n\n return {\n state,\n roundId,\n roundStartedAt: ledger.roundStartedAt ?? null,\n finished,\n productsWrittenThisRound: written !== null ? written.length : null,\n ledgerFilesMissing: ledgerMissing,\n products,\n dir: root,\n note: ROUND_NOTE,\n };\n}\n\n/**\n * 「台账点了名、盘上却没有」是**第三种事**,措辞不许与下面那四档任何一条重合(连子串都不许):\n * 带戳索引的 `missing` = 那份产物**根本没有**(这一轮没写到那里),下一步是重跑 recon;\n * 本档 = **本轮写过、现在却找不到了**,下一步是先问「这份基线是怎么拷过来的」。\n * 把两者说成同一句话,顾问就会去重跑一个其实拷漏了的基线,而漏的那几份下次还会漏。\n */\nexport const LEDGER_MISSING_WORD = '本轮写过、现在却**找不到了**(拷贝 / 裁剪时漏带,或事后被删)';\n\nexport const PRODUCT_STATE_WORDS: Record<Exclude<RoundProduct['state'], 'this-round'>, string> = {\n 'older-round': '是**别的轮次**留下的',\n unstamped: '在盘上,但**没有轮次戳**(更早版本的 kit 写的)',\n missing: '**不在盘上**',\n unreadable: '在盘上但**读不出 JSON**',\n};\n\n/**\n * 两个 roundId 的时间差 —— 「旧到哪一轮」要说得出**多旧**才有用。\n * 解析不出来就不说(宁可少说一句,也不许说错一个时间)。\n */\nexport function roundAge(roundId: unknown, other: unknown): string {\n if (typeof roundId !== 'string' || typeof other !== 'string') return '';\n const a = parseRoundStamp(roundId.split('-')[0] ?? '');\n const b = parseRoundStamp(other.split('-')[0] ?? '');\n if (!a || !b) return '';\n const seconds = Math.trunc((a.getTime() - b.getTime()) / 1000);\n // 起跑时刻不早于本轮 —— 说不出「早多久」就别说。(同秒起跑的两轮靠随机后缀区分,\n // 它们的 roundId 不同但时刻相同,此时只报 id 不报时差。)\n if (seconds <= 0) return '';\n // 🔴 秒级差别不许渲染成 `0h00m`:那读起来像「同一时刻」,而它恰恰是\n // 「两轮挨得很近」——中断后立刻重跑正是这个形态(真机实测两轮相隔 25s)。\n if (seconds < 60) return `,比本轮早 ${seconds}s`;\n return `,比本轮早 ${Math.floor(seconds / 3600)}h${two(Math.floor((seconds % 3600) / 60))}m`;\n}\n\n/** 把 `inspectRound` 的结论说成人话。三档措辞**互不重叠**,读者不必回头查文档。 */\nexport function renderRound(result: RoundResult): string {\n const lines: string[] = [`基线:${result.dir}`];\n if (result.state === 'unknown') {\n lines.push(\n '状态:unknown —— 这份基线里**一个 roundId 都没有**(更早版本的 kit 爬的)。',\n '⇒ 判不了它是不是一轮完整产物。**别把它当 complete,也别当 mixed** ——' +\n '重跑一次 recon 才会带上轮次身份。',\n );\n return `${lines.join('\\n')}\\n`;\n }\n\n lines.push(\n `本轮 roundId:${result.roundId ?? '(无)'}(起跑 ${\n (result.roundStartedAt as string | null) ?? '未知'\n })`,\n );\n if (result.state === 'complete') {\n lines.push(\n `状态:complete —— ${result.products.length} 份带戳的产物同属这一轮,` +\n `本轮共落盘 ${result.productsWrittenThisRound} 个文件。`,\n '⚠️ complete 说的是「**同一轮**」,不是「全量爬过」:定向重爬(`--model X`)' +\n '同样会把三份产物整体重写,照样是 complete。全量与否看 gates.json 的 ' +\n `\\`coverage.${KIT_CARRY_OVER_KEY}\\`。`,\n );\n return `${lines.join('\\n')}\\n`;\n }\n\n lines.push('状态:mixed —— 这份基线**不是一轮完整跑出来的**:');\n for (const p of result.products) {\n if (p.state === 'this-round') continue;\n let detail = PRODUCT_STATE_WORDS[p.state];\n if (p.state === 'older-round') {\n detail += `(roundId ${p.roundId}${roundAge(result.roundId, p.roundId)})`;\n }\n lines.push(` · ${p.path} ${detail}`);\n }\n if (result.finished === false) {\n lines.push(\n ' · 本轮的 productsWrittenThisRound 没有落盘 ⇒ 这一轮**没跑完**' +\n '(Ctrl-C / 断网 / 进程被杀都会这样)',\n );\n }\n const gone = result.ledgerFilesMissing;\n if (gone?.count) {\n // 🔴 点名到文件,且**给全量条数**:只说「有文件缺失」等于让顾问自己去比 1567 行。\n // 名字最多列 5 个(再多在终端里是刷屏),但条数与「查了几份」永远给全 ——\n // 完整名单在结构化结果的 `ledgerFilesMissing.files` 里,不靠这行渲染保真。\n const shown = gone.files.slice(0, 5).join('、');\n const more = gone.count > 5 ? '(仅列前 5 个,完整名单见 ledgerFilesMissing.files)' : '';\n lines.push(\n ` · 台账点名的 ${gone.checked} 份产物里,有 **${gone.count}** 份${LEDGER_MISSING_WORD}:${shown}${more}`,\n );\n }\n lines.push(\n '⇒ 🔴 别拿这些产物里「没有某个模型」当结论 —— 那可能只是**本轮没写到那里**,' +\n '与「产品里真的没有」同形。重跑一次完整 recon。',\n );\n return `${lines.join('\\n')}\\n`;\n}\n\nexport interface CarryOver {\n mode: 'full' | 'targeted';\n onlyModel: string | null;\n modelsRecrawled: string[];\n modelsCarriedOver: string[];\n gatesCarriedOver: number;\n problems: string[];\n topologyRecomputed: boolean;\n note: string;\n}\n\n/**\n * 定向重爬时,把上一轮产物里**其它模型**的部分读回来。\n *\n * 🔴 不做这一步,`--model X` 就是拿单个模型的产物**整体覆写**全量基线:\n * 实测「全量 recon(2 模型)→ `--model Alpha`」后 `index/fields.jsonl` 由 2 行变 1 行、\n * `gates.json` 只剩 1 个模型,而 `models.json` 仍写着 2、`meta/Bravo.model.json` 还躺在那儿\n * —— 产物自相矛盾,且没有任何迹象。\n *\n * 🔴 问题**只记不抛**:读不回旧产物时,定向重爬应当退化成「只有这个模型」的基线,\n * 而不是整条命令失败 —— 但退化这件事必须写进产物,否则它与「上一轮本来就只有这个模型」同形。\n */\nfunction collectCarryOver(\n layout: ReconLayout,\n freshMetas: Record<string, Record<string, unknown>>,\n): {\n carriedMetas: Record<string, Record<string, unknown>>;\n carriedGates: Gate[];\n problems: string[];\n} {\n const problems: string[] = [];\n const carriedMetas: Record<string, Record<string, unknown>> = {};\n const metaDir = path.join(layout.dir, 'meta');\n const suffix = `.${DEFAULT_META_TYPE}.json`;\n let names: string[] = [];\n try {\n names = readdirSync(metaDir).filter((n) => n.endsWith(suffix)).sort();\n } catch (err) {\n // 目录不存在是正常的(首次就用 --model 跑)。别的 IO 错照旧抛。\n if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err;\n }\n for (const name of names) {\n const key = name.slice(0, -suffix.length);\n if (key in freshMetas) continue; // 本轮重爬过,用新的\n const file = path.join(metaDir, name);\n let body: unknown;\n try {\n body = JSON.parse(readFileSync(file, 'utf8'));\n } catch (e) {\n problems.push(`${name}: 读不回上一轮的 meta(${(e as Error).message}),该模型这次不进索引`);\n continue;\n }\n if (isRecord(body)) carriedMetas[key] = body;\n else {\n problems.push(\n `${name}: 上一轮的 meta 载荷是 ${Array.isArray(body) ? 'list' : body === null ? 'null' : typeof body}` +\n ' 不是对象,该模型这次不进索引',\n );\n }\n }\n\n const carriedGates: Gate[] = [];\n const oldGates = readJsonSoft(path.join(layout.dir, 'gates.json'));\n if (oldGates === undefined) {\n // 没有上一轮 gates.json 是正常的(首次就用 --model 跑)。记一句,不当故障。\n problems.push('gates.json: 没有可沿用的上一轮闸表(不存在或读不出 JSON)');\n }\n const oldList = isRecord(oldGates) && Array.isArray(oldGates.gates) ? oldGates.gates : [];\n for (const g of oldList) {\n if (isRecord(g) && !(String(g.model) in freshMetas)) carriedGates.push(g as unknown as Gate);\n }\n\n return { carriedMetas, carriedGates, problems };\n}\n\n/** 定向重爬时给 `models.md` 加一条抬头。产物是会被**单独拷走**的文件。 */\nfunction carryOverBanner(carryOver: CarryOver): string {\n if (carryOver.mode !== 'targeted') return '';\n return (\n `> ⚠️ **本轮是定向重爬(\\`--model ${carryOver.onlyModel}\\`),不是全量。**\\n` +\n `> 这一轮真正爬的只有:${carryOver.modelsRecrawled.join(', ') || '(无)'};\\n` +\n `> 另有 **${carryOver.modelsCarriedOver.length}** 个模型的字段与 ` +\n `**${carryOver.gatesCarriedOver}** 条只读闸是**沿用上一轮产物**,不是这一轮观测到的。\\n` +\n carryOver.problems.map((p) => `> - ⚠️ ${p}\\n`).join('') +\n '\\n'\n );\n}\n\nexport interface RunReconOptions {\n state?: string;\n onlyModel?: string | null;\n /** 服务端认定的 token 归属。落进 `valuedomain/_index.json`,`null` = 调用方没告诉我。 */\n identity?: unknown;\n}\n\nexport interface ReconSummary {\n driftSinceLastRecon: string[] | null;\n modelsQueried: number;\n metasCrawled: number;\n unavailable: number;\n gates: number;\n fieldRows: number;\n fieldsDropped: number;\n carryOver: CarryOver;\n crawledAtUnknown: number;\n topology: Record<string, unknown>;\n valuedomain: Record<string, unknown>;\n mapping: Record<string, unknown>;\n round: Record<string, unknown>;\n coverage: GateCoverage & { [KIT_CARRY_OVER_KEY]: CarryOver };\n dir: string;\n}\n\nexport async function runRecon(\n probe: ReconProbe,\n layout: ReconLayout,\n opts: RunReconOptions = {},\n): Promise<ReconSummary> {\n const state = opts.state ?? '';\n const onlyModel = opts.onlyModel ?? null;\n\n // 🔴 轮次身份在**打第一发请求之前**定下:它是这一轮的身份,不是产物的属性。\n // 放到写盘时再取,中断在写盘之前的那些形态就没有身份可言了。\n const started = new Date();\n const roundId = newRoundId(started);\n\n const modelsBody = await crawlModels(probe);\n // 🔴 走同一道门 `modelKeys()`:`models[]` 里混进一个非 dict 时,就地 `.get(...)`\n // 会在**已经爬完、正要落最后一份产物**的时候炸掉,前面所有网络工作全部白费。\n // ⚠️ 顺带把顺序从「服务端返回序」变成「排序」:探针与闸表本来就按 key 排序,\n // 这里跟着排能让两次爬取的**请求顺序**也确定,出问题时日志可比对。\n let keys = modelKeys(modelsBody);\n if (onlyModel) {\n keys = keys.filter((k) => k === onlyModel);\n if (keys.length === 0) {\n // 🔴 **写盘之前**就拒绝。「打错了一个模型名」与「这个模型确实什么都没有」\n // 必须分得开(GC3):前者一个字节都不该落盘,后者才走正常编排产出空索引。\n const total = Array.isArray(modelsBody.models) ? modelsBody.models.length : 0;\n throw new TargetModelNotFound(\n `--model '${onlyModel}' 在模型清单里一个都没匹配上(清单共 ${total} 个模型)。` +\n '已拒绝写盘,现有基线原样保留。请核对模型名大小写与拼写,' +\n '或先跑一次不带 --model 的全量 recon。',\n );\n }\n }\n\n const { metas: freshMetas, unavailable } = await crawlMeta(probe, keys);\n const gateResult = await collectActionGates(probe, keys, state);\n\n const carried = onlyModel\n ? collectCarryOver(layout, freshMetas)\n : { carriedMetas: {} as Record<string, Record<string, unknown>>, carriedGates: [] as Gate[], problems: [] as string[] };\n\n // ⚠️ 两边**互斥**(`collectCarryOver` 里 `if (key in freshMetas) continue`),所以这里的\n // 合并顺序**观测不到** —— 实测:把顺序反过来跑全套用例**一条都不红**,另有探针跑全套\n // 确认两个键集零交集。真正承重的是那条互斥本身,它由 `carryOver.modelsCarriedOver`\n // 钉住(用例断言它恰是 `['Bravo']`;把那句 continue 删掉会红)。\n // 顺序仍写成「fresh 在后」,是为了让读者一眼看出万一互斥被破坏时**谁该赢**。\n const metas: Record<string, Record<string, unknown>> = { ...carried.carriedMetas, ...freshMetas };\n const gates: Gate[] = [...carried.carriedGates, ...gateResult.gates];\n\n // 三个收集器形状一致 —— 都返回 (产物, 被丢了什么)。\n const { rows, dropped } = extractFieldRows(metas);\n\n const carryOver: CarryOver = {\n mode: onlyModel ? 'targeted' : 'full',\n onlyModel,\n modelsRecrawled: Object.keys(freshMetas).sort(),\n modelsCarriedOver: Object.keys(carried.carriedMetas).sort(),\n gatesCarriedOver: carried.carriedGates.length,\n problems: carried.problems,\n // 🔴 整图产物的诚实标注。语义见 CARRY_NOTE 与 topology.json 的 recomputedNote:\n // false ≠「没算」,而是「算过、边集里有一部分输入是沿用的 meta」。\n topologyRecomputed: !onlyModel,\n note: CARRY_NOTE,\n };\n const coverage: GateCoverage & { [KIT_CARRY_OVER_KEY]: CarryOver } = {\n ...gateResult.coverage,\n [KIT_CARRY_OVER_KEY]: carryOver,\n };\n\n // 🔴 只写**这一轮真爬的** meta,不回写沿用的那些。两个理由:\n // ① 没爬就别声称爬过 —— 回写会把沿用文件的 mtime 刷成「刚刚」;mtime 是产物的诚实\n // 属性,不该由一次没发生的爬取去改写;\n // ② 沿用文件本来就是上一轮写的,内容逐字相同,回写没有任何收益。\n //\n // 🔴 **台账排在第一份产物之前**(评审真机复现)。原先它排在 `writeCrawlProducts` /\n // `writeGates` / `writeFieldsIndex` **之后**,于是有一个真实窗口:中断落在这中间时,\n // `models.json` / `meta/*` / `gates.json` / `index/*` 已经是**新一代**(这四样都不带\n // roundId、也不在 ROUND_STAMPED_PRODUCTS 里),而根部台账与三份索引仍全是**上一轮**的、\n // 彼此自洽 ⇒ `inspectRound` 判 **complete**。那正是本刀要治的病本身,只不过工具还给它\n // 盖了个章 —— **主动判错比沉默不判更坏**。真机量到的窗口 ≈ 0.14s / 14.6s ≈ 1%\n // (models.json +0.000s → index/fields.jsonl +0.128s,而 valuedomain +6.9s /\n // mapping +7.9s / fingerprint +8.7s),且它会随模型数与磁盘变慢而变长。\n //\n // 🔴 原先排在后面的唯一约束是「要先读上一版来沿用时间戳」—— 而**读与写可以拆开**:\n // `writeCrawledAt` 内部先读再落盘,这一刻盘上还是上一轮的 `_kitCrawledAt.json`,读得到;\n // `collectCarryOver` 读的是 `meta/*` 与 `gates.json`,也都还没被覆盖(它排在更前面)。\n // ⇒ 提前它不破坏任何前提。\n const crawledAt = writeCrawledAt(\n layout,\n Object.keys(freshMetas).sort(),\n Object.keys(carried.carriedMetas).sort(),\n roundId,\n started,\n );\n const crawlWritten = writeCrawlProducts(layout, modelsBody, freshMetas, unavailable);\n const gatesPath = writeGates(layout, gates, coverage);\n const fieldsPath = writeFieldsIndex(layout, rows);\n const mdPath = writeModelsMd(\n layout,\n carryOverBanner(carryOver) + renderModelsMd(modelsBody, metas, gates, dropped),\n );\n\n // 🔴 本轮台账的第一段。meta 那一批**不能**按「盘上有没有这个文件」判 ——\n // 沿用上一轮的 meta 文件同样在盘上(定向重爬时它们恒在),拿 exists 判会把\n // 「上一轮写的」算成「本轮写的」,正是这份台账要区分的那件事。\n // 权威判据来自写入方自己:`writeCrawlProducts` 把落盘被拒的 modelKey **就地追加**\n // 进了 `unavailable`(error=unsafe-model-key),减掉它们剩下的就是真写出去的那些。\n const rejectedMetas = new Set(\n unavailable.filter((e) => e?.error === 'unsafe-model-key').map((e) => e.modelKey),\n );\n const products: string[] = [\n crawlWritten.models!,\n crawlWritten.unavailable!,\n KIT_CRAWLED_AT_FILE,\n relOf(layout, gatesPath),\n relOf(layout, fieldsPath),\n relOf(layout, mdPath),\n ];\n for (const k of Object.keys(freshMetas).sort()) {\n if (!rejectedMetas.has(k)) products.push(`meta/${k}.${DEFAULT_META_TYPE}.json`);\n }\n\n // ── topology / valuedomain / mapping ──────────────────────────────────────\n // 🔴 落在这里而不是紧跟 extractFieldRows:下面这一段**要打网络**(码表分页 +\n // 每模型的 mutation-capability),而上面那几份产物早就算完了。先把算完的落盘,\n // 一旦下面这段在客户 VPN 上炸掉,顾问手上至少还有 models/meta/gates/fields 四份,\n // 而不是整轮白跑。编排的**依赖顺序**没变,换的只是「已算完的先落盘」。\n\n // 🔴 名册 ⊇ metas 的键:节点集取名册,`refEdges` 才能把「目标不在名册」这一档\n // 分流进 dangling。定向重爬时名册也是**本轮**取回的,旧的只有 metas 那一半。\n const known = new Set([...Object.keys(metas), ...modelKeys(modelsBody)]);\n const { edges, dangling } = refEdges(metas, known);\n\n // 🔴 `modelsWithMeta` 传的是 `metas`(**本轮进了边集计算的那些**),不是 `freshMetas`:\n // 定向重爬时沿用的 meta 同样贡献了边,它们的出边是**可知**的(只是来自上一轮观测,\n // 那件事由 `_kitCarryOver` / `recomputedThisRound` 另行披露,两个维度不许混)。\n // 名册里而不在这批里的节点,出边一条都算不出来,却与「爬过且真的无依赖」同形地\n // 躺在 waves[0] 里 —— 传了它,产物才点得出名字(8087 实测 761 − 742 = 19 个)。\n const topo = buildTopology(edges, dangling, [...known].sort(), Object.keys(metas).sort());\n topo.recomputedThisRound = !onlyModel;\n topo.modelsRecrawledThisRound = Object.keys(freshMetas).sort();\n topo.recomputedNote = TOPOLOGY_RECOMPUTE_NOTE;\n // 🔴 这份产物会被**单独拷走**,所以轮次身份要长在它自己身上,而不是只写在根部台账里。\n topo.roundId = roundId;\n products.push(relOf(layout, writeTopology(layout, topo)));\n\n const entries = modelEntries(modelsBody);\n const { tables: codeTables, problems: vdProblems } = await collectCodeTables(probe, entries);\n const enumRows = collectEnumDomains(metas);\n const { written: vdWritten, writeProblems: vdWriteProblems } = writeValuedomain(\n layout,\n codeTables,\n enumRows,\n {\n identity: opts.identity,\n // `modelEntriesSeen` 不许省:名册加载失败给出空 list 时,产物会与「名册里一条\n // DYNAMIC 都没有」逐字节同形(GC3)。null 是「调用方没告诉我」,这里我们知道。\n modelEntriesSeen: entries.length,\n carryOver,\n roundId,\n // 取数期台账也要进产物:与落盘期的 writeProblems 分两个键,\n // 两侧现在都是「摘要给条数 + 产物给明细」。\n problems: vdProblems,\n },\n );\n\n // 🔴 喂给 `buildMapping` 的是「对照表**真的写出来了**的那些」,不是 `collectCodeTables`\n // 的键集。两者的差额(modelKey 含 `/` 或以 `.` 开头被本地判据拒掉、layout 越界闸拦下)\n // **全部是悬空指针** —— 而 mapping 自己的 WORDING 写着「指一个不存在的文件比不指更糟」。\n // ⚠️ 判据是**文件在不在**,不是「这张码表爬成功了没有」:爬失败的表照样落一份\n // `completeness=unavailable` 的文件,指向它是**有效**的。\n // 🔴 **两个条件缺一不可**:光判「路径在不在 `written` 里」会被 kit 自己的产物文件骗过\n // —— `written` 里**恒有** `valuedomain/_index.json`(索引自己写的那一份),于是一张\n // `modelKey=\"_index\"` 的 DYNAMIC 码表**明明被拒了**,却因为那个路径存在而被判成\n // 「文件写出来了」,mapping 照着发出指针,两条对账链**都报干净**,而顾问点开拿到的是\n // 索引不是对照表。⇒ 先问 `valuedomain` **自己那道判据**(同一个函数,不是抄一份),\n // 再看路径。\n const writtenPaths = new Set(vdWritten);\n const codeTableFiles = new Set(\n Object.keys(codeTables).filter(\n (k) =>\n unsafeTableKeyReason(k) === null &&\n writtenPaths.has(path.join(layout.dir, 'valuedomain', `${k}.json`)),\n ),\n );\n\n const { mutation, problems: mutProblems } = await collectMutation(probe, Object.keys(metas).sort());\n const sheets = buildMapping(metas, edges, mutation, codeTableFiles);\n // 🔴 第二条**独立**证据链:上面那个推导(用 writeValuedomain 的返回值反推)自己也可能\n // 错,而错法恰好是不报错的。这里不吃那个推导,直接按 sheet 里印出来的指针去看盘。\n // 两条链互不依赖,差集才有意义 —— 只有一条时,「推导错了」与「产物没问题」同形。\n const danglingPointers = auditValueDomainPointers(layout, sheets);\n // 🔴 三份台账**同时**进产物与摘要:摘要跑完就没了,而产物是被单独拷走的那一半。\n products.push(...vdWritten.map((p) => relOf(layout, p)));\n const mappingPaths = writeMapping(layout, sheets, {\n problems: mutProblems,\n danglingValueDomain: danglingPointers,\n carryOver,\n roundId,\n });\n products.push(...mappingPaths.map((p) => relOf(layout, p)));\n // `writeMapping` 的契约:`_index.json` **在最后**,其余每条对应一份写成的 sheet。\n // 写不下来的 modelKey 不抛也不静默 —— 名字在 `mapping/_index.json` 的 `unsafeModelKeys`\n // 里,这里只报差额,好让「ls mapping/ 与 modelCount 对不上」在摘要里就看得见。\n const sheetsWritten = Math.max(mappingPaths.length - 1, 0);\n\n // 🔴 **先读旧指纹再覆盖**,把「上次爬取以来环境变没变」算出来放进 summary。\n // 这不是漂移门:**本处只记录、不阻断**。阻断那一半住在 `apply` 的漂移门里。\n let previous: unknown = readJsonSoft(path.join(layout.dir, 'fingerprint.json'));\n // 🔴 首次爬取(没有旧指纹)与「比对过、没变化」是**两个不同的结论**,\n // 不能都表示成空 list(GC3)。前者用 `null`。\n if (!isRecord(previous)) previous = null;\n\n const current = await computeFingerprint(probe);\n products.push(relOf(layout, layout.writeJson('fingerprint.json', current)));\n const drift =\n previous === null\n ? null\n : compareFingerprint(previous as Record<string, unknown>, current as unknown as Record<string, unknown>);\n\n // 🔴 **一轮的最后一笔**:写完台账才算跑完。放在这里不是随意 —— 它同时是\n // 「这一轮跑到了终点」的唯一见证(见 `finishRound` 的说明)。\n const productsWritten = finishRound(layout, crawledAt, roundId, started, products);\n // 摘要里也给一份判断:跑完那一刻就能看见「我刚写出来的这份基线是不是完整一轮」。\n //\n // 🔴 **摘要不是唯一落点,也不该是**:被中断的那一轮压根不会打印摘要,而那恰恰是\n // 要诊断的那一轮。所以同一个判断有三条路,且都不依赖这次进程还活着:\n // ① 产物自证:四份文件各带 roundId,`grep` 就能对账(命令写在 `_kitCrawledAt.json`\n // 的 roundNote 里 —— 产物被单独拷走时它跟着走);\n // ② `inspectRound()` / `renderRound()`:零网络、只读文件,给三档结论 + 人话;\n // ③ 本摘要字段:跑完那一刻的复核。\n const roundResult = inspectRound(layout.dir);\n\n const byCompleteness: Record<string, number> = {};\n for (const c of COMPLETENESS) {\n byCompleteness[c] = Object.values(codeTables).filter((v) => v.completeness === c).length;\n }\n\n return {\n driftSinceLastRecon: drift,\n modelsQueried: keys.length,\n metasCrawled: Object.keys(freshMetas).length,\n unavailable: unavailable.length,\n gates: gates.length,\n fieldRows: rows.length,\n // 🔴 被丢掉的字段必须有落点。摘要给条数,models.md 给明细 ——\n // 「记下来了但没人看得见」等于没记。\n fieldsDropped: dropped.length,\n // 🔴 索引里有多少行不是这一轮爬的,必须能一眼看出来 —— 否则定向重爬产出的\n // 基线与全量基线在摘要里同形。\n carryOver,\n // 🔴 时间戳未知的模型数必须能一眼看到:它是「沿用了但不知道多老」的落点,\n // 与「刚爬的」不许合并(GC3)。\n crawledAtUnknown: Object.values(crawledAt).filter((v) => v === null).length,\n // 🔴 三段统计一律**带上自己的 problems / 差额**,不许只报好数字(no silent caps):\n // 明细在各自的产物文件里,摘要给条数 + 指向文件,两处缺一份都等于没记\n // (只进摘要 ⇒ 产物被单独拷走后就没了;只进产物 ⇒ 跑完那一刻看不见)。\n topology: {\n nodes: topo.nodeCount,\n edges: topo.edgeCount,\n cycles: (topo.cycles as unknown[]).length,\n selfLoops: (topo.selfLoops as unknown[]).length,\n unordered: (topo.unordered as unknown[]).length,\n blockedByCycles: (topo.blockedByCycles as unknown[]).length,\n // 三个「没进依赖图」的口子都要报数:edgeCount 与图里用掉的边数的差额\n // 就是它们仨,不报的话没人说得清差在哪。\n dangling: (topo.dangling as unknown[]).length,\n // 🔴 「出边不可知」的节点数(接线后恒是数字,不再是 null)。明细在 topology.json\n // 的 nodesWithoutMeta.models 里,摘要给条数 —— 两处缺一份都等于没记。\n nodesWithoutMeta: (topo.nodesWithoutMeta as { count: number }).count,\n excludedEdges: (topo.excludedEdges as { count: number }).count,\n edgesOutsideNodes: (topo.edgesOutsideNodes as { count: number }).count,\n recomputedThisRound: topo.recomputedThisRound,\n file: 'topology.json',\n },\n valuedomain: {\n codeTableCount: Object.keys(codeTables).length,\n enumFieldCount: enumRows.length,\n // 四档全列(含产不出来的那一档),缺键会被读成「这个 kit 还不懂这一档」。\n byCompleteness,\n // 🔴 两份台账**不许合并**:problems 是取数时的(爬不到 / 没取满 / 分页可疑),\n // writeProblems 是落盘时的(modelKey 当不了文件名)。前者「有这张表但内容残」,\n // 后者「压根没有这份文件」—— 下一步动作完全不同。\n problems: vdProblems,\n writeProblems: vdWriteProblems,\n file: 'valuedomain/_index.json',\n },\n mapping: {\n modelCount: sheets.length,\n sheetsWritten,\n // 差额恒有值:`ls mapping/ | wc -l` 与 modelCount 对不上时,这里先给出数,\n // 名字在 mapping/_index.json 的 unsafeModelKeys 里。\n sheetsNotWritten: sheets.length - sheetsWritten,\n problems: mutProblems,\n // 🔴 悬空的 valueDomain 指针。空列表 = **对过账且一条都没有**,\n // 不是「没对过」(GC3)—— 这份对账是 codeTableFiles 那条推导的独立复核。\n danglingValueDomainPointers: danglingPointers,\n file: 'mapping/_index.json',\n },\n // 🔴 轮次身份 + 本轮台账的条数。产物与摘要两处落点:明细在 `_kitCrawledAt.json` 里,\n // 摘要给结论与条数。\n round: {\n roundId,\n state: roundResult.state,\n productsWritten: productsWritten.length,\n file: KIT_CRAWLED_AT_FILE,\n // 中断过的基线不会走到这里,所以必须告诉顾问**事后**怎么问同一个问题。\n // ⚠️ 这里写的是**今天真能敲**的那条,且**两个模式都要 grep**:只看 roundId\n // 会把「四行对齐」读成「没问题」,而真机实测存在「对齐但没跑完」那一档。\n // 🔴 命令头与 roundNote 同源(`GREP_RECIPE_HEAD`),不抄第二份。\n recheckWith: `cd ${layout.dir} && ${GREP_RECIPE_HEAD} ${KIT_CRAWLED_AT_FILE} ${ROUND_STAMPED_PRODUCTS.join(' ')}`,\n // 子命令形态(零网络)—— 它给的是 complete/mixed/unknown 三档人话 + 退出码,\n // 比 grep 多一层判读。🔴 命令名与 roundNote 的 ② 同源(`ROUND_SUBCOMMAND`),\n // 不抄第二份:抄两份的表现是「产物教的命令与摘要教的不一样」。\n recheckCommand: `${ROUND_SUBCOMMAND} --dir ${layout.dir} --output table`,\n },\n coverage,\n dir: layout.dir,\n };\n}\n","/**\n * 定向重爬的披露标记。\n *\n * 🔴 `_kit` 前缀:这一节是**工具加的**,产品从不发它。下游遍历产物顶层键的人\n * 必须一眼看得出哪些是服务端说的、哪些是我们说的。\n *\n * 🔴 **为什么单独成一个模块**:有**三份**产物要写这一节 —— `gates.json`(编排层写)、\n * `mapping/_index.json`、`valuedomain/_index.json`(后两份由各自模块写)。\n * 而 `mapping` / `valuedomain` 不许反过来 import 编排层(那是它们的上层,import 会成环)。\n * 剩下的选择只有两个:把字面量抄三份,或者收到一个三方都在其下的地方。\n * 抄三份必然漂移,而漂移的表现是**顾问 grep `_kitCarryOver` 只搜到两份产物** ——\n * 第三份看着就像「这一轮没有沿用」,正是这一节存在的意义所要防的那种误读。\n */\nexport const KIT_CARRY_OVER_KEY = '_kitCarryOver';\n","/**\n * 不可用条目的 note 表(契约清单 §3.1 的 `recon.note` 那一族)。\n *\n * 🔴 note 不是装饰:顾问在客户现场拿到的就是 `unavailable.json`,\n * 分类串(`not-found` / `network` / `bad-response`…)本身给不出下一步。\n * 每一类的措辞必须**互斥**,且把顾问导向**不同**的排查方向——\n * 两类都写「网络层失败」,等于把两条完全不同的路合成一条死路。\n */\n\n/**\n * 产品对 `@HiddenFromRegistry` 模型恒返 404。\n * 2026-08-16 实测(8087 / 租户 13):761 个模型里 19 个如此(18 个 *Index +\n * 1 个 WorkspaceCheckpointRevisionModel)。**这是分类结果,不是故障。**\n */\nconst NOTE_404 =\n 'HTTP 404:多为 @HiddenFromRegistry 型模型(如各种 *Index),' +\n '产品对其恒返 DATA_NOT_FOUND。属正常分类结果,非故障';\n\n/**\n * 🔴 `network` **不等于「网络坏了」**。四类根本不是网络故障的成因也落这里,\n * 而「网络层失败」这种文案会把顾问直接支去查 VPN。\n *\n * 🔴 **「已实测四类」不等于「一共就四类」,这个区别是承重的**:批量路径的兜底是\n * 「接住一切异常」,**按构造就是开放的**。所以 note **不许**写成\n * 「排除这几条就是网络问题」那种带排除法的完整枚举——那是把一句不准但**没声称完整**的话,\n * 升级成一句**声称完整**的断言,而那个前提并不成立,反而更容易被照着做错。\n */\nconst NOTE_NETWORK =\n '未收到 HTTP 响应(恒 status=-1)。🔴 本桶**不只装网络故障**;下面是**已实测**四类,' +\n '**不保证列全**(批量路径兜底接住一切异常,本层任何新异常都会落这里):' +\n '① modelKey 含空格/控制字符 → URL 畸形,工具侧问题;' +\n '② 端点漏写 http:// 且带端口(如 localhost:8087)→「unknown url type」,配置问题;' +\n '③ https + 自签/内网 CA → TLS 证书问题,配 CA 或改 http,查链路是白查;' +\n '④ 工具自身异常 → 批量路径兜成本桶,**去看 stderr 里的堆栈**。' +\n '都不像才轮到查 VPN / DNS / 链路。' +\n '占比也是判据:整批全落本桶 → 端点/证书/网络;只个别几条 → 那个 modelKey 或工具 bug';\n\n/**\n * 🔴 分类串的**字面意思会骗人**,note 必须先把这件事说破:\n * 401/403/404 与 5xx 单独分档,**其余非 2xx(400/409/422…)一律落这里**,\n * 正文是不是完好的 JSON 根本不参与判定——写成「收到响应但不是可解析的 JSON」\n * 会让顾问去查一个不存在的解析问题。\n *\n * 🔴 note 里那个错误信封的形状**回源自线上**(2026-08-16 打三个端点实测):\n * 顶层键集恒为 `['error','success']`,`error` 键集恒为 `['code','details','message']`。\n * ⚠️ 别按 Java 源码「改正」这句:`success` 由更外层包装补上,源码里 grep 不到它——\n * **回源到源码 ≠ 回源到线上**。反过来 `error.field` 在 Java DTO 里**有**,\n * 但三次实测一次都没出现(null 被省略),所以它只能以「源码有、未观测」的措辞出现。\n */\nconst NOTE_BAD_RESPONSE =\n '分类按 status 走,**不代表正文一定不是 JSON**。按 status 分流:' +\n 'status=-1 → 请求**根本没发出去**(同样**不保证列全**):' +\n 'modelKey 含**非 ASCII**(如中文)→ 发送阶段编码失败;' +\n '或端点漏写 http:// 且**不带端口**(如 myserver)→「unknown url type」。' +\n '⚠️ 含**空格**的 modelKey、以及漏 scheme 但**带端口**的端点,落的是 network 桶不在这里;' +\n 'status=2xx → 服务端返回了空体、非 JSON 正文或 JSON null(「没给到数据」,非「空集合」)' +\n '—— 只有这一档的**分类原因**是解析失败;' +\n '其余 status(400/409/422 等未被单独分档的非 2xx)→ 诊断在**状态码**里,' +\n '正文很可能是一份**完好**的业务错误信封' +\n '({\"error\":{\"code\",\"message\",\"details\"},\"success\":false},人话在 details 里;' +\n 'error 还可能带 field,该键回源自 Java DTO、2026-08-16 实测三端点均未出现),' +\n '值得看一眼;但本工具的 GET 路径不保留非 2xx 的正文(产物里没有它),' +\n '要看这段人话得照原样重打一次该请求(curl 即可)。也可能是网关/反代吐的 HTML 兜底页';\n\nexport const UNAVAILABLE_NOTES: Record<string, string> = {\n 'not-found': NOTE_404,\n forbidden: 'HTTP 403:当前账号对该模型无权限。产物覆盖面因此受限,须记进权限画像',\n unauthorized: 'HTTP 401:token 失效或未认证',\n network: NOTE_NETWORK,\n 'bad-response': NOTE_BAD_RESPONSE,\n 'server-error': '服务端 5xx',\n};\n\n/**\n * `UNAVAILABLE_NOTES` 的兜底。\n *\n * 🔴 只写「未分类失败」等于把分类串本身也丢了——那是顾问唯一能拿去搜的线索 ⇒ 说清下一步。\n * 🔴 **本函数不下「新分类串只可能来自某处」这种封闭断言**:那条封闭性由\n * 「每个失败类都有 note,集合恰好相等」那条守卫断成 `==`,不在这里用散文声称。\n */\nexport function unclassifiedNote(error: string): string {\n return (\n `未分类失败:分类串 ${JSON.stringify(error)} 不在工具的 note 表里。` +\n '本层里给这个参数喂值的是 `crawlMeta` 与 `collectActionGates` 两处,' +\n '两处传的都是同一进程里探针给的分类串(同一份安装)——' +\n '在这条链路上它与 note 表本该逐一对上。别的来源本模块**没有查过**;' +\n '把上面这个分类串原样报回来'\n );\n}\n\n/** 取 note:认识就给分类 note,不认识就给可搜索的兜底。 */\nexport function noteFor(error: string): string {\n return UNAVAILABLE_NOTES[error] ?? unclassifiedNote(error);\n}\n","/**\n * 侦察层第一跳:模型清单与元数据爬取(契约清单 §3.1,32 条)。\n *\n * 🔴 本模块最贵的不是「怎么发请求」,是两条记账纪律:\n * ① **每个模型都必须到达一个终态**(`metas.size + unavailable.length === 请求数`);\n * ② **`declared` vs `observed` 双向对账**。\n * 少一个模型不会报错,只会让基线悄悄变小——这正是「静默漏登」的形态。\n * 而 ① 对「某个模型被悄悄漏掉」是**盲的**(漏掉的既不在分子也不在分母),\n * 所以必须有 ② 那条互不依赖的第二证据链。\n */\nimport { MODELS_PATH } from '../fingerprint';\nimport type { Outcome, ReconProbe } from '../probe';\nimport type { ReconLayout } from '../layout';\nimport { LayoutContainmentError } from '../layout';\nimport { noteFor } from './notes';\n\n/**\n * 🔴 `_kit` 前缀:这个键是**工具加的**,产品从不发它。\n * 下游遍历 `models.json` 顶层键的人必须一眼看得出哪些是服务端说的、哪些是我们说的。\n */\nexport const KIT_PREMISE_KEY = '_kitPremiseCheck';\n\n/**\n * 🔴 默认形态是**实测打过**的裸路径 `/api/models/{k}/meta`\n * (2026-08-16 打满 761 个 key:742×200 + 19×404,5.0s)。\n * `type=` 只在调用方显式要别的视图(list/info/view)时才追加——\n * 给默认路径缀一个从没实测过的 `?type=model`,等于把「实测成立的请求」换成「推出来的请求」。\n */\nexport const DEFAULT_META_TYPE = 'model';\n\nexport function metaPath(modelKey: string, metaType: string = DEFAULT_META_TYPE): string {\n // 🔴 模型名**不做 URL 转义**:转义会让 `meta/<modelKey>.json` 不再能按 modelKey grep 回去,\n // 而畸形 URL 本身已被探针归成 status=-1,note 里有分流判据。\n const base = `/api/models/${modelKey}/meta`;\n return metaType === DEFAULT_META_TYPE ? base : `${base}?type=${metaType}`;\n}\n\nexport interface UnavailableEntry {\n modelKey: string;\n metaType: string;\n status: number;\n error: string;\n note: string;\n /** 只有 `unexpected-shape` 那一支带它。 */\n bodyPreview?: string;\n}\n\nexport interface PremiseCheck {\n totalCountMatchesListLength: boolean;\n responseShape: string;\n status: number;\n error: string;\n collidedWithServerKey: boolean;\n serverValue?: unknown;\n}\n\nfunction modelRows(body: unknown): Array<Record<string, unknown>> {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return [];\n const rows = (body as Record<string, unknown>).models;\n if (!Array.isArray(rows)) return [];\n return rows.filter((m): m is Record<string, unknown> => Boolean(m) && typeof m === 'object' && !Array.isArray(m));\n}\n\n/** 响应体的形状名,用于 `responseShape`:区分 object / array / string / null…… */\nfunction shapeOf(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'list';\n return typeof value === 'object' ? 'dict' : typeof value;\n}\n\n/**\n * 爬模型清单,并**就地**注入一份前提自证。\n *\n * 🔴 服务端返 list/str 时不能直接往上挂键——那在客户现场就是一条裸堆栈。\n * 回落成空清单,但**不许**让空清单伪装成健康:`totalCount(0) === len([])` 恰好成立,\n * 一份垃圾响应就此看起来像「爬到了,就是空的」。\n */\nexport async function crawlModels(probe: ReconProbe): Promise<Record<string, unknown>> {\n const out = await probe.get(MODELS_PATH);\n const healthy = Boolean(out.body) && typeof out.body === 'object' && !Array.isArray(out.body);\n const body: Record<string, unknown> = healthy\n ? { ...(out.body as Record<string, unknown>) }\n : { totalCount: 0, typeCounts: {}, models: [] };\n const rows = Array.isArray(body.models) ? (body.models as unknown[]) : undefined;\n const premise: PremiseCheck = {\n // 前提显式断言:totalCount 与 models 长度不符时,后续任何「共 N 个模型」都不成立。\n totalCountMatchesListLength: Boolean(healthy && rows !== undefined && body.totalCount === rows.length),\n responseShape: shapeOf(out.body),\n status: out.status,\n error: out.error,\n // 撞键与否**恒有值**:没有这个键时「没撞」与「没检查过」在产物里同形。\n collidedWithServerKey: Object.prototype.hasOwnProperty.call(body, KIT_PREMISE_KEY),\n };\n if (premise.collidedWithServerKey) {\n // 🔴 就地注入不许**静默覆盖**服务端的同名键:服务端说了什么,\n // 是这份基线里最不能被工具吃掉的东西。撞键时原值原样留在 serverValue 里。\n premise.serverValue = body[KIT_PREMISE_KEY];\n }\n body[KIT_PREMISE_KEY] = premise;\n return body;\n}\n\nexport interface CrawlMetaResult {\n metas: Record<string, Record<string, unknown>>;\n unavailable: UnavailableEntry[];\n}\n\n/**\n * 逐模型爬 meta。**每个 key 都会到达一个终态**:要么进 `metas`,要么进 `unavailable`。\n */\nexport async function crawlMeta(\n probe: ReconProbe,\n modelKeys: string[],\n metaType: string = DEFAULT_META_TYPE,\n): Promise<CrawlMetaResult> {\n const paths = new Map<string, string>();\n for (const key of modelKeys) paths.set(metaPath(key, metaType), key);\n const results = probe.getMany\n ? await probe.getMany([...paths.keys()])\n : await sequentialGet(probe, [...paths.keys()]);\n\n const metas: Record<string, Record<string, unknown>> = {};\n const unavailable: UnavailableEntry[] = [];\n for (const [path, key] of paths) {\n const out = results[path];\n if (out === undefined) {\n // 并发丢任务:必须是一条**显式记录**,否则表现为「这个模型好像没有 meta」。\n unavailable.push({\n modelKey: key,\n metaType,\n status: -1,\n error: 'not-crawled',\n note: '并发批次里没有返回该 path',\n });\n continue;\n }\n const isObject = Boolean(out.body) && typeof out.body === 'object' && !Array.isArray(out.body);\n if (out.error === 'ok' && isObject) {\n metas[key] = out.body as Record<string, unknown>;\n continue;\n }\n if (out.error === 'ok') {\n // 🔴 HTTP 成功但载荷形状不对。这一支必须与「HTTP 失败」**分开**记:\n // 混进下面那支会得到一条 `error: \"ok\"` 的 unavailable 条目,读起来自相矛盾,\n // 且把两种完全不同的成因糊成一团——顾问看了不知道该找网关还是找我们。\n unavailable.push({\n modelKey: key,\n metaType,\n status: out.status,\n error: 'unexpected-shape',\n note:\n `HTTP ${out.status} 成功但载荷是 ${shapeOf(out.body)},` +\n '不是 meta 对象(载荷形状问题,不是取数失败)',\n bodyPreview: JSON.stringify(out.body).slice(0, 200),\n });\n continue;\n }\n // 🔴 **刻意不加 `bodyPreview`**:这一支拿不到正文,加一个叫 bodyPreview 却装不下 body\n // 的字段比不加更糟——它会让读产物的人以为「看过正文了,没线索」。\n // 理由要说准:本函数走的全是 GET,非 2xx 那档探针**压根没保留正文**。\n unavailable.push({ modelKey: key, metaType, status: out.status, error: out.error, note: noteFor(out.error) });\n }\n return { metas, unavailable };\n}\n\n/** 探针没有 `getMany` 时的退化路径:逐条打,键仍按原始 path 回填。 */\nexport async function sequentialGet(probe: ReconProbe, paths: string[]): Promise<Record<string, Outcome>> {\n const out: Record<string, Outcome> = {};\n for (const p of paths) out[p] = await probe.get(p);\n return out;\n}\n\nexport interface Reconciliation {\n declaredUnavailable: string[];\n observedUnavailable: string[];\n declaredNotObserved: string[];\n observedNotDeclared: string[];\n agrees: boolean;\n note: string;\n}\n\n/**\n * 两条**互不依赖**的证据链对拍:清单自己声明的 `error` vs 我们实际打出来的失败。\n *\n * 🔴 差集**有方向**,两个方向指向完全相反的结论:\n * · `declaredNotObserved`(清单说坏、我们没记)⇒ **漏爬**;\n * · `observedNotDeclared`(清单说好、我们爬坏了)⇒ 权限 / 超时 / 网关,**不是**隐藏模型。\n */\nexport function reconcileUnavailable(\n modelsBody: unknown,\n unavailable: UnavailableEntry[],\n): Reconciliation {\n const declared = [\n ...new Set(modelRows(modelsBody).filter((m) => m.error).map((m) => String(m.modelKey))),\n ].sort();\n const observed = [...new Set(unavailable.map((e) => e.modelKey).filter(Boolean))].sort();\n const dset = new Set(declared);\n const oset = new Set(observed);\n return {\n declaredUnavailable: declared,\n observedUnavailable: observed,\n declaredNotObserved: declared.filter((k) => !oset.has(k)),\n observedNotDeclared: observed.filter((k) => !dset.has(k)),\n agrees: declared.length === observed.length && declared.every((k) => oset.has(k)),\n note:\n 'declaredNotObserved = 模型清单声明不可用、但本轮没有对应失败记录 ⇒ 漏爬;' +\n 'observedNotDeclared = 模型清单说可用、本轮却失败 ⇒ 查权限/超时/网关,' +\n '不是「发现了隐藏模型」',\n };\n}\n\n/**\n * 落盘。\n *\n * ⚠️ 本函数**会向 `unavailable` 追加**(越界 modelKey 是在写盘这一步才发现的)。\n * 这是刻意的:追加发生在 `unavailable.json` 落盘之前,且调用方随后用它的长度报摘要——\n * 就地追加让产物与摘要两边同时正确。\n */\nexport function writeCrawlProducts(\n layout: ReconLayout,\n modelsBody: Record<string, unknown>,\n metas: Record<string, Record<string, unknown>>,\n unavailable: UnavailableEntry[],\n metaType: string = DEFAULT_META_TYPE,\n): Record<string, string> {\n const written: Record<string, string> = {};\n layout.writeJson('models.json', modelsBody);\n written.models = 'models.json';\n\n for (const [key, body] of Object.entries(metas)) {\n try {\n layout.writeJson(`meta/${key}.${metaType}.json`, body);\n } catch (e) {\n if (!(e instanceof LayoutContainmentError)) throw e;\n // 🔴 modelKey 是现爬的,可能含 `..`。落盘闸**硬失败**而不是把\n // `meta/../models.json` 静默写成产物根下的 models.json 顶掉模型名册。\n // 这里必须把抛出来的那一档转成一条**显式产物**——否则这个模型就从基线里凭空消失了,\n // 而「没写下来」和「这个模型不存在」同形。\n unavailable.push({\n modelKey: key,\n metaType,\n status: 200,\n error: 'unsafe-model-key',\n note: e.message,\n });\n }\n }\n written.meta = 'meta/';\n\n // 「爬过且为空」必须是一个**显式产物**:crawled=true + count=0。\n // reconciliation 一并落盘:顾问手上只有磁盘上这几个文件,没有跑这轮爬取的进程。\n layout.writeJson('unavailable.json', {\n crawled: true,\n count: unavailable.length,\n entries: unavailable,\n reconciliation: reconcileUnavailable(modelsBody, unavailable),\n });\n written.unavailable = 'unavailable.json';\n return written;\n}\n","/**\n * 把爬下来的 meta 摊成**一行一字段**的 `index/fields.jsonl`(契约清单 §3.2,21 条)。\n *\n * 这份产物就是检索面的全部:顾问在一台没有代码仓库、没有安装包的机器上,\n * 只能靠 `grep 部门 index/fields.jsonl` 把一个中文说法落到 `{model, key, type, enums}`。\n * 所以这里每一条纪律都直指「grep 得到 / grep 不到」,而不是「结构好不好看」。\n *\n * 🔴 **没有静默丢弃**:摊不进索引的字段一律进 `dropped` 台账并返回给调用方。\n * 本模块与两个兄弟收集器同形(`crawlMeta → {metas, unavailable}`、\n * `collectActionGates → {gates, coverage}`):三者都把「被丢了什么」和「收到了什么」一起交出来。\n */\n\n/**\n * 🔴 凡是**工具自己合成**、产品从没发过的取值,必须一眼看得出是我们补的。\n * 没有这个标记时,`{label:\"在职\"}`(value 是我们编的)与\n * `{value:\"在职\",label:\"在职\"}`(服务端真发的)产出**逐字相同**,\n * 顾问会拿一个我们编的值去拼筛选条件。\n */\nexport const KIT_VALUE_SOURCE = '_kitValueSource';\n\nexport interface EnumRow {\n value: string;\n label: string;\n [KIT_VALUE_SOURCE]?: string;\n}\n\nexport interface FieldRow {\n model: string;\n key: string;\n name: string;\n type: string;\n isRequired: boolean;\n isFlex: boolean;\n columnDefinition: string;\n synonyms: string[];\n description: string;\n enums: EnumRow[];\n}\n\nfunction typeName(v: unknown): string {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'list';\n return typeof v === 'object' ? 'dict' : typeof v;\n}\n\n/**\n * 判读一份 meta 载荷:返回 `{fields, problem}`,`problem` 是人话描述(undefined = 正常)。\n *\n * 🔴 **这是 `fields` 与 `modelsmd` 的唯一判读口径**。两个消费者吃的是**同一份 metas**\n * (runner 一次爬取喂给两边)——各写各的判读,就会出现「一边记台账继续跑、\n * 另一边裸抛异常」这种半容忍状态,而它只在客户现场那一次才暴露,\n * 那里恰恰**没有源码也没有栈可读**。\n *\n * 🔴 「没有 `fields` 键」与「`fields` 显式是 null」是**两个不同的事实**:\n * 前者是「这个模型确实没字段」(正常,不进台账);后者是服务端发了个形状异常的载荷,\n * 与发 `{}` / 发字符串同级,必须留痕。不单独判就会被静默当成「没有字段」,\n * 失败方向是**低报**。\n */\nexport function metaFields(body: unknown): { fields: unknown[]; problem?: string } {\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return { fields: [], problem: `meta 载荷不是对象(${typeName(body)})` };\n }\n const obj = body as Record<string, unknown>;\n if (!Object.prototype.hasOwnProperty.call(obj, 'fields')) return { fields: [] };\n const fields = obj.fields;\n if (fields === null) {\n return { fields: [], problem: 'fields 是 null(服务端显式发了 null,与「没有 fields 键」不是一回事)' };\n }\n if (!Array.isArray(fields)) return { fields: [], problem: `fields 不是数组(${typeName(fields)})` };\n return { fields };\n}\n\n/**\n * 枚举取 `enums`(**不是 `options`**),每项保留 `value` + `label`。\n *\n * 🔴 2026-08-16 实测(8087,147 个模型 / 2676 个字段):字段级键里**根本没有 `options`**,\n * 枚举挂在 `enums` 上(120 个字段有,占 4%)。元素形态**有三种**\n * (`{key,label,value}`×329、`{key,label,type,value}`×83、`{effect,key,label,type,value}`×28)\n * ⇒ **不许按固定形状解包**:只取需要的两个键,多余的忽略。\n * label 一起留:顾问搜的是「已离职」这种**业务话术**,不是 `TERMINATED`。\n *\n * 🔴 **认不出的元素也留下,一个都不丢**。因果要写对:「现网 440 个元素 100% 带 value」\n * 是**运气**,不是免疫——它挡不住 number / null / 只有 label 的对象,\n * 而这几种一旦出现,按固定形状解包会让整条枚举**无痕消失**,产物看起来却完全正常。\n *\n * 🔴 **但补出来的东西必须认领**:退化路径产出的行带 `_kitValueSource`。\n * 少了它,三种互不相同的输入(元素是 null / 是裸串 \"null\" / 是 `{value:\"null\"}`)\n * 产出**逐字相同**,而其中两种的 value 是我们编的。\n * 服务端真发了 value 的行**不带**这个键:干净的那条路必须保持干净。\n */\nexport function enumRows(field: Record<string, unknown>): EnumRow[] {\n const items = field.enums;\n if (!Array.isArray(items)) return [];\n const out: EnumRow[] = [];\n for (const o of items) {\n if (o && typeof o === 'object' && !Array.isArray(o)) {\n const rec = o as Record<string, unknown>;\n const value = rec.value;\n const label = rec.label;\n if (value !== undefined && value !== null) {\n // 产品自己发的取值:不加任何标记。\n out.push({ value: String(value), label: String(label ?? '') });\n } else if (label !== undefined && label !== null) {\n // 缺 value 的对象:value 退回 label,至少让这条枚举还能被中文 grep 到。\n out.push({ value: String(label), label: String(label), [KIT_VALUE_SOURCE]: 'label' });\n } else {\n out.push({ value: JSON.stringify(o), label: '', [KIT_VALUE_SOURCE]: 'repr:dict' });\n }\n continue;\n }\n // 非对象(裸标量 / number / null …):整体字符串化留痕,并把**原始类型**记进标记——\n // 否则 `null` 与裸串 `\"null\"` 在产物里同形。\n // ⚠️ 这些形态**未在现网观测到**(实测 440 个元素全是对象),留这一支是为了「认不出也别丢」。\n out.push({ value: String(o), label: '', [KIT_VALUE_SOURCE]: `element:${typeName(o)}` });\n }\n return out;\n}\n\nexport interface FieldExtraction {\n rows: FieldRow[];\n /** 人话描述的台账,如 `\"ModelX: 第 3 个字段没有可用的 key(null)\"`。 */\n dropped: string[];\n}\n\n/**\n * 摊平。\n *\n * 🔴 `dropped` 不能只被「记下来」——没有落点的台账等于没记。\n * 它的两处落点是 `models.md` 的警告段与 runner 摘要,缺一不可。\n */\nexport function extractFieldRows(metas: Record<string, unknown>): FieldExtraction {\n const rows: FieldRow[] = [];\n const dropped: string[] = [];\n for (const [model, body] of Object.entries(metas)) {\n // 判读走 `metaFields` 单一口径:渲染侧吃同一份 metas,两边不许各判各的。\n const { fields, problem } = metaFields(body);\n if (problem) {\n // 前提由 `crawlMeta` 保住(只有对象载荷才进 metas),但**保不住别的喂法**:\n // 从磁盘 `meta/*.json` 直接读回来的调用方不受那条前提约束。留台账,不崩也不静默。\n dropped.push(`${model}: ${problem},整个模型没有字段进索引`);\n continue;\n }\n fields.forEach((f, index) => {\n const i = index + 1;\n if (!f || typeof f !== 'object' || Array.isArray(f)) {\n dropped.push(`${model}: 第 ${i} 个字段不是对象(${typeName(f)})`);\n return;\n }\n const rec = f as Record<string, unknown>;\n const key = rec.key;\n if (typeof key !== 'string' || !key) {\n // 没有 key 的字段既不能被调用、也无法与产品侧对账——但它消失这件事必须看得见。\n dropped.push(`${model}: 第 ${i} 个字段没有可用的 key(${JSON.stringify(key) ?? String(key)})`);\n return;\n }\n rows.push({\n model,\n key,\n // 🔴 中文名的键是 `name`,**不是 `label`**(实测 2676/2676 个字段没有一个带 label)。\n // 本仓字段命名规范也是「名称字段统一用 name,禁止 label」。\n name: str(rec.name),\n type: str(rec.type),\n // 🔴 必填的键是 `isRequired`,**不是 `required`**(实测 required 出现 0 次)。\n // 缺省一律显式 false,不用 null——null 会把「没这属性」和「值是假」混成一件事。\n isRequired: Boolean(rec.isRequired ?? false),\n isFlex: Boolean(rec.isFlex ?? false),\n // `columnDefinition` 是实测存在的存储侧信息(14% 的字段有);原计划的 `storage` 键**不存在**。\n columnDefinition: str(rec.columnDefinition),\n // 🔴 顾问真正会 grep 的是业务话术:字段级 synonyms 与 description。\n // 它们稀疏但**恒发**(空就是 [] / \"\")——「有这一列且为空」与「没这一列」是两个值。\n synonyms: Array.isArray(rec.synonyms) ? rec.synonyms.filter(Boolean).map(String) : [],\n description: str(rec.description),\n enums: enumRows(rec),\n });\n });\n }\n return { rows, dropped };\n}\n\nfunction str(v: unknown): string {\n return v === undefined || v === null || v === false ? '' : String(v);\n}\n\n/** 索引落在 `find` 将要读的那个路径上。 */\nexport function writeFieldsIndex(\n layout: { writeJsonl(rel: string, rows: Iterable<unknown>): string },\n rows: FieldRow[],\n): string {\n // 走 layout.writeJsonl:不转义非 ASCII 收口在那一处——\n // 中文被转义成 \\uXXXX 就等于整个检索层失效。\n // 签名只吃 rows:台账的落点是 models.md 与 runner 摘要,不混进 JSONL 行里。\n return layout.writeJsonl('index/fields.jsonl', rows);\n}\n","/**\n * 只读闸(action gates)的爬取与落盘(契约清单 §3.5,29 条)。\n *\n * `readOnly` 是产品里**唯一**让 AI 工具 `hcm_invoke_read_action` 免用户确认直接执行的开关。\n * 判错的代价不是「基线不准」,是 **AI 静默写客户生产库**。所以本模块一切从严、一切 fail-closed:\n * 凡是没能确凿看到「显式声明只读 + 全部候选都只读 + 端点自己也不认为有分歧」的条目,一律不进闸表。\n */\nimport type { Outcome, ReconProbe } from '../probe';\nimport type { ReconLayout } from '../layout';\nimport { sequentialGet } from './crawl';\n// 🔴 复用 §3.1 的分类 note 表而不是抄一份:两份表必然漂移,而顾问在现场唯一能照着做的\n// 分流判据就写在这些 note 里。/actions 与 /meta 的失败成因是同一套 —— 2026-08-16 在 8087/租户 13\n// 打满 **761 个 key 双向对照**:meta 非 200 = 19、actions 非 200 = 19,且两个方向的差集都为空,\n// 即两边的不可用集合**集合相等**(不是「计数相等」—— 计数相等推不出集合相等,别把它读成同一句话)。\nimport { noteFor } from './notes';\n\n/** 🔴 措辞纪律:只能说「在声明影响面内未观测到变化」,不许说「证明无副作用」。 */\nexport const WORDING =\n '本文件登记的是**显式声明**为只读的动作。kit 只能走 RBAC 裁剪后的 REST、' +\n '观测窗口有限,因此任何结论的口径都是「在声明影响面内未观测到变化」,' +\n '而**不是**「无副作用」。guard=declared-only 的条目连服务端静态守卫都没有,' +\n '只有一句声明。';\n\n// guard 二值。**没有第三档**:任何「看起来也挺安全」的中间态都会变成放行的理由。\n/** source 全部是 java ⇒ `ReadOnlyActionIsReallyReadOnlyTest` 守着。 */\nexport const GUARD_ARCH = 'declared+arch-guard';\n/** 其余 ⇒ 只有一句声明,无服务端静态守卫。 */\nexport const GUARD_DECLARED_ONLY = 'declared-only';\nexport const ARCH_GUARDED_SOURCE = 'java';\n\n/**\n * 「声明了只读、却被 fail-closed 规则拒掉」的动作必须留痕:不留痕的话,\n * 「我们拒了它」与「压根没这个动作」在产物里同形,下一个人无从复核我们拒得对不对。\n */\nexport type RejectReason =\n | 'conflict'\n | 'conflict-undeclared'\n | 'candidate-not-read-only'\n | 'no-candidates'\n | 'malformed-candidate'\n | 'malformed-key'\n | 'malformed-action';\n\n/**\n * 🔴 键是 `RejectReason` 联合而不是 `string`:`Record<string, string>` 下\n * `REJECT_NOTES[reason]` 的类型是 `string | undefined`,一个拼错的 reason 会在产物里\n * 落成 `\"note\": undefined`(该键直接消失)。收成联合让拼错在**编译期**就红。\n */\nexport const REJECT_NOTES: Record<RejectReason, string> = {\n conflict:\n '端点标了 conflict=true:同 key 的候选彼此不一致。不一致就不能免确认执行 —— ' +\n '这正是「为 A 放行、执行 B」那条洞',\n 'conflict-undeclared':\n '端点没有明确说 conflict=false(键缺失或不是布尔)。fail-closed 不猜:' +\n '缺声明按有分歧处理。与 readOnly 同一条纪律 —— 缺键不等于「没问题」',\n 'candidate-not-read-only':\n '聚合说只读,但至少一个候选没有显式 readOnly=true。' +\n '多候选聚合是 all 不是 any:一个只读候选不能把会写库的动作抬成闸',\n 'no-candidates': '声明了 readOnly=true 却没有任何候选背书。没有候选 = 没有任何东西可核',\n 'malformed-candidate': '候选不是对象(网关改写 / 版本漂移?),无法核对它的 source 与 readOnly',\n 'malformed-key': '动作没有可用的 key —— 没有 key 就无法调用,也无法与产品侧对账',\n 'malformed-action': '动作条目不是对象,无法判读它是不是只读闸',\n};\n\nexport interface Gate {\n model: string;\n key: string;\n /**\n * action 级的中文名。\n *\n * 🔴 **必须显式兜成 `null`,不能留 `undefined`**:这是 Python → TS 的一个真陷阱。\n * Python 的 `action.get(\"label\")` 缺键时给 `None`,落 JSON 是 `\"label\": null`;\n * TS 里 `action.label` 缺键是 `undefined`,而 `JSON.stringify` 会把它**整个键删掉** ——\n * 产物里的 gate 条目从 9 键变成 8 键,「闸条目形状稳定」那条契约就此静默失效。\n */\n label: string | null;\n source: string[];\n state: string | null;\n readOnly: true;\n conflict: false;\n candidates: unknown[];\n guard: string;\n}\n\nexport interface RejectedEntry {\n model: string;\n key: string | null;\n reason: RejectReason;\n note: string;\n}\n\nexport interface GateUnavailable {\n modelKey: string;\n status: number;\n error: string;\n note: string;\n bodyPreview?: string;\n}\n\nexport interface GateCoverage {\n scope: 'state-scoped';\n state: string | null;\n statesEchoed: string[];\n stateObserved: boolean;\n stateEchoAgrees: boolean;\n stateRequested: string;\n modelsWithoutStateEcho: number;\n sources: string[];\n modelsQueried: number;\n duplicateModelKeys: number;\n modelsAnswered: number;\n modelsUnavailable: number;\n accountingBalanced: boolean;\n unavailable: GateUnavailable[];\n rejected: RejectedEntry[];\n endpointGates: false;\n note: string;\n}\n\n/**\n * `/api/models/{k}/actions`,带 state 时缀查询串。\n *\n * 模型名不做 URL 转义(与 `metaPath` 同规矩):转义会让产物不再能按 modelKey grep 回去,\n * 而畸形 URL 由探针归成 status=-1 的 bad-response。\n * **state 是必须转义的** —— 它是顾问随手敲进来的值,一个空格或 `&` 就能把请求打歪\n * (且假探针的前缀路由看不出来,只有断实际 URL 的用例逮得住)。\n */\nexport function actionsPath(modelKey: string, state = ''): string {\n const base = `/api/models/${modelKey}/actions`;\n if (!state) return base;\n return `${base}?${new URLSearchParams({ state }).toString()}`;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\n\nfunction reject(out: RejectedEntry[], model: string, key: unknown, reason: RejectReason): void {\n out.push({\n model,\n key: typeof key === 'string' && key ? key : null,\n reason,\n note: REJECT_NOTES[reason],\n });\n}\n\nexport interface GateResult {\n gates: Gate[];\n coverage: GateCoverage;\n}\n\n/**\n * 返回 `{gates, coverage}`。只收 `readOnly === true` 且过得了全部 fail-closed 关的条目。\n *\n * gate 条目是 **9 键**:`model` / `key` / `label` / `source` / `state` / `readOnly` /\n * `conflict` / `candidates` / `guard`。`label` 是 action 级的中文名(meta 的**字段**级叫\n * `name`,两者不同源,别统一),它是「本地可 grep 的基线」这句承诺在闸表这一块的落点。\n */\nexport async function collectActionGates(\n probe: ReconProbe,\n modelKeys: string[],\n state = '',\n): Promise<GateResult> {\n // 去重保序:重复的 key 会让「扫了 N 个模型」与实际请求数对不上,\n // 而 modelsQueried 恰恰是下游用来判断覆盖面的那个数。\n const keys = [...new Set(modelKeys)];\n const paths = new Map<string, string>();\n for (const k of keys) paths.set(actionsPath(k, state), k);\n const results: Record<string, Outcome> = probe.getMany\n ? await probe.getMany([...paths.keys()])\n : await sequentialGet(probe, [...paths.keys()]);\n\n const gates: Gate[] = [];\n const rejected: RejectedEntry[] = [];\n const unavailable: GateUnavailable[] = [];\n const sources = new Set<string>();\n const statesEchoed = new Set<string>();\n let noStateEcho = 0;\n let answered = 0;\n\n for (const [path, model] of paths) {\n const out = results[path];\n if (out === undefined) {\n // 并发丢任务:必须显式记,否则表现成「这个模型没有只读闸」。\n unavailable.push({\n modelKey: model,\n status: -1,\n error: 'not-crawled',\n note: '并发批次里没有返回该 path',\n });\n continue;\n }\n if (out.error !== 'ok') {\n unavailable.push({ modelKey: model, status: out.status, error: out.error, note: noteFor(out.error) });\n continue;\n }\n const body = out.body;\n if (!isRecord(body) || !Array.isArray(body.actions)) {\n // 🔴 HTTP 成功 ≠ 扫成了。载荷不是对象、或根本没有 actions 数组时,\n // 我们**不知道**这个模型有没有只读闸 —— 记成 answered 就等于替它宣布「没有闸」。\n // 注意与 `\"actions\": []` 的区别:那是爬过且确实为空,算 answered。\n unavailable.push({\n modelKey: model,\n status: out.status,\n error: 'unexpected-shape',\n note:\n `HTTP ${out.status} 成功但载荷是 ${body === null ? 'null' : Array.isArray(body) ? 'list' : typeof body}` +\n '、或其中没有 actions 数组,无法判读该模型的只读闸' +\n '(载荷形状问题,不是取数失败)',\n bodyPreview: JSON.stringify(body).slice(0, 200),\n });\n continue;\n }\n\n answered += 1;\n // 端点回显的 state 才是这条记录的覆盖面;我们请求的那个只是意图,两者不一定相等。\n const echoed = typeof body.state === 'string' ? body.state : null;\n if (echoed === null) noStateEcho += 1;\n else statesEchoed.add(echoed);\n\n for (const action of body.actions) {\n if (!isRecord(action)) {\n reject(rejected, model, null, 'malformed-action');\n continue;\n }\n const cands: unknown[] = Array.isArray(action.candidates) ? action.candidates : [];\n for (const c of cands) {\n if (isRecord(c) && typeof c.source === 'string' && c.source) sources.add(c.source);\n }\n\n // fail-closed 第一关:没有**显式** readOnly=true 的一律按写动作对待,\n // 无论它叫 preview / check / test。缺键不是「默认只读」。\n if (action.readOnly !== true) continue;\n\n const key = action.key;\n if (typeof key !== 'string' || !key) {\n reject(rejected, model, key, 'malformed-key');\n continue;\n }\n // 🔴 要的是**明确的** conflict=false,不是「没标 true 就算没分歧」——\n // 与上面 readOnly 那关同一条纪律:缺键不等于「没问题」。\n if (action.conflict !== false) {\n reject(rejected, model, key, action.conflict === true ? 'conflict' : 'conflict-undeclared');\n continue;\n }\n if (cands.length === 0) {\n // `[].every()` 恒为 true —— 零候选会从下面那关直接漂过去,必须先拦。\n reject(rejected, model, key, 'no-candidates');\n continue;\n }\n if (!cands.every((c) => isRecord(c))) {\n reject(rejected, model, key, 'malformed-candidate');\n continue;\n }\n if (!cands.every((c) => (c as Record<string, unknown>).readOnly === true)) {\n // 🔴 聚合是 all 不是 any:一个只读候选不能把会写库的 action 抬成闸。\n reject(rejected, model, key, 'candidate-not-read-only');\n continue;\n }\n\n // 🔴 逐个候选比 source,不走 Set 去重。危险的不是 Set 本身,而是 **Set + 过滤**:\n // - 朴素 `new Set(cands.map(c => c.source))`:缺 source 的候选贡献一个 `undefined`\n // ⇒ 集合是 {\"java\", undefined} ⇒ 仍判 declared-only,**不会**误升;\n // - 但下面那行 `source` 字段为了产出干净列表**带了 typeof 过滤**。同一套过滤逻辑\n // 一旦被复用到 guard 判定上,缺 source 的候选就被整个滤没了 ⇒ 集合恰好等于 {\"java\"}\n // ⇒ 误升成 arch-guard。逐个比不吃这一套:没有 source 的候选一票否决。\n const allJava = cands.every((c) => (c as Record<string, unknown>).source === ARCH_GUARDED_SOURCE);\n gates.push({\n model,\n key,\n // 缺 label 兜成 null,见 `Gate.label` 上那条注释(undefined 会让键整个消失)。\n label: typeof action.label === 'string' ? action.label : null,\n source: [\n ...new Set(\n cands\n .map((c) => (c as Record<string, unknown>).source)\n .filter((s): s is string => typeof s === 'string' && Boolean(s)),\n ),\n ].sort(),\n state: echoed,\n readOnly: true,\n conflict: false,\n candidates: cands,\n guard: allJava ? GUARD_ARCH : GUARD_DECLARED_ONLY,\n });\n }\n }\n\n const echoedList = [...statesEchoed].sort();\n const coverage: GateCoverage = {\n // 🔴 永远是 state-scoped:产品没有「列出可用 state」的端点(实测约 200 个取值),\n // 「全集」不是一个可兑现的词,所以本产物只覆盖本次请求的那一个 state。\n scope: 'state-scoped',\n // 端点回显的 state。恰好一个取值时记那个值;多个模型回显不一致时记 null 并把\n // 全部取值摊在 statesEchoed 里 —— 取最后一个赢家会让产物声称一个并不成立的覆盖面。\n state: echoedList.length === 1 ? (echoedList[0] as string) : null,\n statesEchoed: echoedList,\n // 🔴 「回显了空 state」(state=\"\") 与「一个模型都没答上来」是两回事,\n // 不许靠 null 一个值兼职两种含义。\n stateObserved: statesEchoed.size > 0,\n stateEchoAgrees: statesEchoed.size <= 1,\n stateRequested: state,\n modelsWithoutStateEcho: noStateEcho,\n sources: [...sources].sort(),\n modelsQueried: keys.length,\n duplicateModelKeys: modelKeys.length - keys.length,\n modelsAnswered: answered,\n modelsUnavailable: unavailable.length,\n // 前提显式断言:不平就说明有模型既没记成答上来、也没记成失败 —— 「扫了 N 个模型」不成立。\n accountingBalanced: keys.length === answered + unavailable.length,\n unavailable,\n rejected,\n endpointGates: false, // 普通 Controller 端点闸不在本产物内\n note:\n 'scope 恒为 state-scoped:state 不可枚举,本产物只覆盖 stateRequested 请求到的那一个;' +\n 'unavailable = 没扫成的模型(与「这个模型没有只读闸」是两回事);' +\n 'rejected = 声明了只读却被 fail-closed 规则拒掉的动作,它们不是闸;' +\n // 🔴 口径必须写死在产物里:sources 是**全体** action 的候选来源,不是闸的来源。\n // 少这一句,下游拿 sources 不等于 [\"java\"] 当「出现了非 Java 只读闸」的信号就会误判。\n 'sources = 本轮见到的**全部** action(含写动作、含被 rejected 的条目)的候选 source 去重,' +\n '**不是**闸的来源;闸自己的来源看每条 gate 的 source 字段',\n };\n return { gates, coverage };\n}\n\nexport function writeGates(layout: ReconLayout, gates: Gate[], coverage: GateCoverage): string {\n // 「爬过且为空」必须是显式产物:crawled=true + count=0,与「没爬过」(文件不存在)区分开。\n return layout.writeJson('gates.json', {\n wording: WORDING,\n coverage,\n crawled: true,\n count: gates.length,\n gates,\n });\n}\n","/**\n * 强连通分量(Tarjan)。\n *\n * 🔴 **独立成模块,不是为了「分层好看」**:`topology` 里那条「缩点图还有环」的前提自证\n * 在正确实现下**不可达**(缩点图恒为 DAG),唯一能证明它还活着的办法就是短路 SCC 计算。\n * 而模块内的私有函数在 ESM 下换不掉 —— 拆出来之后,测试用 `vi.mock` 替换本模块即可,\n * **生产代码里不必开任何注入口子**(那种为测试而生的扩展点正是 YAGNI 要挡的)。\n */\n\n/**\n * 数组字典序比较。\n *\n * 🔴 **不能用默认 `.sort()`**:JS 对数组元素排序时走 `String(a)`,\n * `['a','b']` 会被拍成 `\"a,b\"` —— 分隔符 `,`(44) 参与比较,与 Python 的\n * 元素逐个比较**不等价**(`['a,b']` 与 `['a','b']` 在 JS 里同形)。\n * 产物要跨语言逐字可 diff,比较必须显式写出来。\n */\nexport function compareStringArrays(a: string[], b: string[]): number {\n for (let i = 0; i < Math.min(a.length, b.length); i += 1) {\n if (a[i]! < b[i]!) return -1;\n if (a[i]! > b[i]!) return 1;\n }\n return a.length - b.length;\n}\n\n/**\n * 迭代版 Tarjan SCC,返回**全部**分量(含单点),每个分量内部已排序。\n *\n * 用迭代而不是递归 —— 但**把话说准**:实测这张图在本模块的遍历序(根节点排序、邻居排序)下\n * DFS **最深只有 6 层**,最长依赖链 12 个模型,离栈上限很远,递归版在**这份数据上不会**炸。\n * ⚠️ 那个 6 是**这个遍历序下**测到的,换个根序能走得更深,真正的上界是节点数(今天 742)。\n * 迭代版买的就是这个上界:越线时递归版的失败形态是栈溢出裸抛,\n * 顾问看到的是一屏 traceback 不是人话。\n *\n * 🔴 邻居按排序遍历:`adj` 的值是 Set,迭代序是插入序而非字典序。\n * 不排序时**分量的产出顺序**会随输入顺序变,而**分量集合**不变 —— 结果对、中间态不稳,\n * 调试时两次跑同一份输入看到不同中间态,会把人引去查根本不存在的不确定性。\n */\nexport function tarjan(nodes: string[], adj: Map<string, Set<string>>): string[][] {\n const index = new Map<string, number>();\n const low = new Map<string, number>();\n const onStack = new Set<string>();\n const stack: string[] = [];\n const result: string[][] = [];\n let counter = 0;\n const neighbours = (n: string): string[] => [...(adj.get(n) ?? [])].sort();\n\n for (const root of nodes) {\n if (index.has(root)) continue;\n index.set(root, counter);\n low.set(root, counter);\n counter += 1;\n stack.push(root);\n onStack.add(root);\n const work: Array<{ node: string; it: string[]; pos: number }> = [\n { node: root, it: neighbours(root), pos: 0 },\n ];\n while (work.length > 0) {\n const frame = work[work.length - 1]!;\n let advanced = false;\n while (frame.pos < frame.it.length) {\n const nxt = frame.it[frame.pos]!;\n frame.pos += 1;\n if (!index.has(nxt)) {\n index.set(nxt, counter);\n low.set(nxt, counter);\n counter += 1;\n stack.push(nxt);\n onStack.add(nxt);\n // 🔴 迭代版的关键:把「递归下去」换成「压栈后 break」,\n // 父节点的剩余邻居留在 `pos` 里,回来接着走。\n // 每次恢复都从头重来的话结果仍然对,但同一批邻居会被反复重走。\n work.push({ node: nxt, it: neighbours(nxt), pos: 0 });\n advanced = true;\n break;\n }\n if (onStack.has(nxt)) {\n // 回边 / 横叉边指向仍在栈上的节点:取它的 index(不是 low)。\n low.set(frame.node, Math.min(low.get(frame.node)!, index.get(nxt)!));\n }\n }\n if (advanced) continue;\n work.pop();\n if (work.length > 0) {\n // 子节点走完,把它的 low 回传给父节点(等价于递归版返回后的那一行)。\n const parent = work[work.length - 1]!.node;\n low.set(parent, Math.min(low.get(parent)!, low.get(frame.node)!));\n }\n if (low.get(frame.node) === index.get(frame.node)) {\n const comp: string[] = [];\n for (;;) {\n const w = stack.pop()!;\n onStack.delete(w);\n comp.push(w);\n if (w === frame.node) break;\n }\n result.push(comp.sort());\n }\n }\n }\n return result;\n}\n\n","/**\n * 模型级依赖图(契约清单 §3.7,33 条)。回答的是「先建哪个模型」,**不是**「先建哪一行」。\n *\n * 🔴 边界:模型级不足以定行级顺序。组织自环只说明「这张表内部有父子序」,\n * 决定不了具体哪一行先写 —— 行级 DAG 是后续刀的 `work/import-plan.json`。\n * 本文件如实标出自环,但**不**假装它能排行。\n *\n * 🔴 边只从 `refEdges` 来(本模块不再判读 `object` / `refer`):两处各判一次\n * 就会长出「一边算 2929 条、另一边算 2926 条」这种谁也说不清的差额。\n *\n * 🔴 **环的下游必须点名**:把「指向环成员的依赖」直接抹掉再分波,被抹掉依赖的模型会落进\n * `waves[0]`,而顾问读 `waves[0]` 就是「可以第一个导」。照错的那份导,结果是引用不存在行的 400。\n * ⇒ 分波跑在**缩点图**(每个强连通分量缩成一个点)上,环成员照旧不进 `waves`,\n * 但它们占的那一层是**实打实占掉的**。\n */\nimport type { ReconLayout } from '../layout';\nimport type { RefEdge, DanglingRef } from './refs';\nimport { compareStringArrays, tarjan } from './scc';\n\n/**\n * 🔴 依据是**字段名约定**,不是产品声明 —— 实测这 1476 个字段的\n * isReadonly/autoGenerate/isList/isInfo 全为 false,元数据里没有任何可编程判据能识别它们。\n * 所以「审计字段是服务端填的」这个**理由**我们证不了,能证的只有**效果**:\n * 见每次运行现算的 `excludedEdges.impact`。\n *\n * 🔴 这已经是**跨产物契约**:`mapping.ts` 也 import 它,改这里会同时移动\n * `topology.json` 与 `mapping/` 两份产物。\n */\nexport const AUDIT_FIELD_KEYS = ['createById', 'updateById'] as const;\nconst AUDIT_SET = new Set<string>(AUDIT_FIELD_KEYS);\n\nexport const WORDING =\n // 🔴 这一句必须在最前面:下面每个键都是**在这张裁剪过的图上**算出来的。\n // 少了它,顾问拿 cycles=[] 会读成「产品里没有互相引用」,而实际是「排掉审计边之后没有」。\n '🔴 全部结论都算在**排除 excludedEdges(审计边)之后**的边集上 —— ' +\n 'cycles / selfLoops / waves / blockedByCycles 无一例外;' +\n 'waves = 模型级拓扑分波,跑在缩点图上,同一波内部可并行,覆盖「全部模型 − unordered」不重不漏;' +\n // 🔴 这里**不能**写成「waves[0] 不依赖任何其他模型」。整个第 0 层都是环分量时,\n // 那一层不出现在 waves 里,waves[0] 于是可能整层都依赖环成员(A→B, B↔C 即是)。\n 'waves[0] 不依赖任何**可定序**模型;' +\n '🔴 但 waves 里有位置**不等于「可以直接导」**:凡是同时出现在 blockedByCycles 里的模型,' +\n '都还依赖着环成员,**必须先把 cycles 按两段式解开**才能导 —— 读 waves 必须连 blockedByCycles 一起读;' +\n 'cycles = 强连通分量(≥2 个模型互相依赖),[] 表示**算过且没有**,不是没算;' +\n 'selfLoops = 自环模型(如组织的 parentId),自环不进 cycles —— 它说明**行级**有父子序,' +\n '模型级并不阻塞,照样进 waves;' +\n 'unordered = 卷进 cycles 因而定不出顺序的模型,它们**不在** waves 里,' +\n '把它们塞进 waves 等于假装定得出顺序;' +\n 'blockedByCycles = 自己不在环里、但(传递地)依赖某个环成员的模型;' +\n 'excludedEdges = 按字段名约定(createById / updateById)排除出依赖图的审计边,原样留着可放回,' +\n '🔴 这条口径在**本次数据上**值多少,看 excludedEdges.impact(含/不含审计边各现算一遍,' +\n '不是任何一个租户的历史数字);' +\n 'edgesOutsideNodes = 两端有一头不在本次模型名册里的边,不进图但也不静默丢,同样原样留着;' +\n 'dangling = 产品声明了引用、但这个引用用不了的字段,**两档,🔴 权威判据是 reason 不是 toModel**:' +\n 'reason=not-in-registry 时 toModel 是**真的模型名**、只是不在 /api/system/models 名册里' +\n '(实测 4 个:ImpactRecord / JobFamily / PerfAssessDimensionInstance / UserModel),照着填会拿到 400;' +\n 'reason=malformed-target 时产品给的目标形态就是坏的(object={\"a\":1} / 5 / [\"T\"] 之类),' +\n '此时 toModel 存的是 **repr(原值)**、是 kit 合成的字符串,**不是模型名** —— ' +\n '拿它去查模型只会查到一个不存在的名字,该字段根本没有可用的引用目标';\n\nexport const NOTE =\n '模型级顺序 ≠ 行级顺序。树形数据(组织、岗位序列)的行序要看后续的 ' +\n 'work/import-plan.json;本文件只保证「模型 A 的第一行写之前,模型 B 已经写完」。' +\n 'waves 里没有 cycles/unordered 的成员,波次编号也**不给它们留空档** —— ' +\n '整层都是环成员的那一层不会出现在 waves 里,所以 waves[0] 未必是「可以第一个导」的那一批:' +\n '先看它与 blockedByCycles 有没有交集。照 waves 顺序执行前,' +\n '必须先按两段式(先插入、引用列留空,再回填)把 cycles 处理掉。';\n\n/** 🔴 产物里唯一解释「为什么少了这些边」的地方,措辞按证得动/证不动分开写。 */\nconst EXCLUDED_NOTE =\n '这些边按**字段名约定**(createById / updateById)排除出依赖图。' +\n '🔴 依据只有命名约定,**不是产品声明**:实测全库 1476 个这样的字段,' +\n 'isReadonly / autoGenerate / isList / isInfo 四个标志全部为 false,' +\n '元数据里没有任何可编程判据能认出「这是服务端填的审计字段」。' +\n '(旁证:有人在现网 CO_0001 的行上观察到 createById 全是系统哨兵 UUID 000…001,' +\n '强烈提示服务端填充 —— 但那是**他人观察、本工具未复核**,且 **recon 这条路**只读不写,' +\n '**证不了**,所以这里不把它当理由。)' +\n '排除的**效果**是量得到的,且**每次运行现算**:见同级的 impact。' +\n '🔴 判定顺序:审计字段**先判**,早于自环判定与名册判定 —— 所以' +\n '① `User.createById → User` 记在这里而不是 selfLoops(否则等于凭空断言「User 有行级父子序」);' +\n '② 一条 createById 指向名册外目标的边也记在这里,**不会**出现在 edgesOutsideNodes 里。' +\n '🔴 如果某个客户环境真的需要顾问自己填 createById / updateById,' +\n // 🔴 不许写成「键与 edges 逐字相同」:`topology.json` 的**顶层没有 edges 键**。\n '这批边就在同级的 edges 数组里,键与 refs 产出的引用边逐字相同(七键:fromModel / fromField / ' +\n 'fieldName / toModel / carrier / labelField / labelFieldSource),放回依赖图即可 —— 排除不等于丢弃。';\n\nconst OUTSIDE_NOTE =\n '边的一端不在本次模型名册里:不进依赖图(进了会凭空多出一个不存在的节点),' +\n '但也不静默丢 —— 静默丢的形态是 edgeCount 与图里用到的边数对不上,' +\n '而这个差额没有任何人看得出来。同级的 edges 是**原样**的引用边' +\n '(键与 refs 产出的引用边逐字相同,七键),' +\n '哪一端出界,拿它的 fromModel / toModel 与模型名册比一下即知。' +\n '按编排(modelKeys ⊇ metas 的键,且 refEdges 已把「目标不在名册」' +\n '分流进 dangling)这里恒为空,实测 0 条。';\n\n/**\n * 🔴 **参考值,不是本次结果**。留着是为了让读者知道这条口径在一个真实中等规模租户上的量级;\n * 本次运行自己的两组数在 `impact.withAudit` / `impact.withoutAudit` 里,是现算的。\n */\nconst REFERENCE_IMPACT = {\n source: '租户 13 / 742 个模型 / 2923 条边,2026-08-16 实测',\n withAudit: { cycles: 4, cycleMembers: 22, selfLoops: 22, blockedByCycles: 717 },\n withoutAudit: { cycles: 3, cycleMembers: 7, selfLoops: 21, blockedByCycles: 23 },\n note:\n '🔴 这是**参考值,不是本次结果** —— 本次的两组数在同级的 withAudit / withoutAudit 里。' +\n '在那个租户上,含审计边时 742 个模型有 717 个(96%)被环挡住、产物等于没用;' +\n '排除后只剩 23 个。别拿这组数去解释你手上这份产物。',\n};\n\ninterface Analysis {\n selfLoops: string[];\n cycles: string[][];\n unordered: string[];\n waves: string[][];\n blockedByCycles: string[];\n excluded: RefEdge[];\n outside: RefEdge[];\n}\n\n/**\n * 在给定边集上算依赖图结构。\n *\n * `excludeAudit=false` 这一路**只**用来算 `excludedEdges.impact` 的对照组 ——\n * 产物本身永远走 `true`。两条路共用同一段代码是承重的:分开写两份,\n * 「对照组」和「产物」就会各自漂移,而 impact 声称的正是两者的差。\n */\nfunction analyze(edges: RefEdge[], nodes: string[], nodeSet: Set<string>, excludeAudit: boolean): Analysis {\n const deps = new Map<string, Set<string>>(nodes.map((n) => [n, new Set<string>()]));\n const selfLoops = new Set<string>();\n const outside: RefEdge[] = [];\n const excluded: RefEdge[] = [];\n for (const e of edges) {\n const a = e.fromModel;\n const b = e.toModel;\n // 🔴 审计边先判,早于自环与名册判定。理由与后果写在 EXCLUDED_NOTE 里(顺序是产物契约的一部分)。\n if (excludeAudit && AUDIT_SET.has(e.fromField)) {\n excluded.push(e);\n continue;\n }\n if (!nodeSet.has(a) || !nodeSet.has(b)) {\n outside.push(e); // 原样,与 excludedEdges 同一形状:放得回去\n continue;\n }\n if (a === b) {\n selfLoops.add(a);\n // 自环不进 deps —— `deps` 的语义是**跨模型**依赖。\n // ⚠️ 别把这一句读成「少了它自环就排不进波次」:删掉它产物**一字不变**,\n // 因为缩点那一步的 `cj !== ci` 会把分量内部边(含自环)一并落掉。\n // 两处都在是「语义在这里、兜底在那里」,不是「少一处就会错」。\n continue;\n }\n deps.get(a)!.add(b);\n }\n\n const comps = tarjan(nodes, deps);\n const compOf = new Map<string, number>();\n comps.forEach((comp, i) => comp.forEach((m) => compOf.set(m, i)));\n const cyclicComps = new Set<number>(comps.map((c, i) => (c.length > 1 ? i : -1)).filter((i) => i >= 0));\n const cycles = [...cyclicComps].map((i) => comps[i]!).sort(compareStringArrays);\n const unordered = [...cyclicComps].flatMap((i) => comps[i]!).sort();\n const cyclic = new Set(unordered);\n\n // 缩点:分量之间的依赖。同一分量内部的边全部落掉(它们正是环本身)。\n const compDeps = new Map<number, Set<number>>(comps.map((_, i) => [i, new Set<number>()]));\n for (const n of nodes) {\n const ci = compOf.get(n)!;\n for (const d of deps.get(n)!) {\n const cj = compOf.get(d)!;\n if (cj !== ci) compDeps.get(ci)!.add(cj);\n }\n }\n\n // Kahn 分波,跑在**缩点图**上 —— 不是「剔掉环成员之后的子图」。\n // 差别正是承重的那一处:环成员占掉的层是实打实占掉的,\n // 依赖它们的模型因此排在环之后,而不是被抹平成零入度。\n const remaining = new Map<number, Set<number>>([...compDeps].map(([i, v]) => [i, new Set(v)]));\n const levelOf = new Map<number, number>();\n let level = 0;\n // 🔴 **有界**循环,不是 `while (remaining.size)`:每轮至少定下一个分量 ⇒ 上界就是分量数。\n // 这一改是为了让「前提自证块被整块删掉」表现为**红**而不是**挂死** ——\n // while 版本删掉下面那个 throw 之后,ready 恒空、remaining 永不变小,测试会一直转,\n // CI 上的形态是 job 超时、零信号。\n for (let round = 0; round <= comps.length; round += 1) {\n if (remaining.size === 0) break;\n const ready = [...remaining].filter(([, d]) => d.size === 0).map(([i]) => i);\n if (ready.length === 0) {\n // 前提自证:缩点图恒为 DAG,这里不该卡住。真卡住只有一种解释 ——\n // `tarjan` 没把互相依赖的模型收进同一个分量(SCC 算错)。\n // 报出来,不要静默丢模型:丢掉的形态是产物看着完整、实际少几个模型。\n const stuck = [...remaining.keys()].map((i) => comps[i]!).sort(compareStringArrays);\n throw new Error(\n '拓扑分波卡住:缩点图里仍然存在环,说明 tarjan 给出的分量不是强连通分量' +\n `(互相依赖的模型没被收进同一个分量)。剩余分量:${JSON.stringify(stuck)}。产物不可信`,\n );\n }\n for (const i of ready) {\n levelOf.set(i, level);\n remaining.delete(i);\n }\n const readySet = new Set(ready);\n for (const d of remaining.values()) for (const r of readySet) d.delete(r);\n level += 1;\n }\n if (remaining.size > 0) {\n // 有界循环的收尾自证:正常路径一定在上界内出清。走到这里说明上面那个 throw 被绕过了。\n // 🔴 措辞与上面那条**故意不同**:两条都喊同一句话时,「主守卫被删掉」会被这条兜底\n // 悄悄接住、测试照样绿。不同措辞让「谁在报」一眼可辨。\n throw new Error(\n `拓扑分波在 ${comps.length + 1} 轮内没有出清,剩余分量 ` +\n `${JSON.stringify([...remaining.keys()].map((i) => comps[i]!).sort(compareStringArrays))};` +\n '分波与 SCC 计算不一致,产物不可信',\n );\n }\n\n // 「被环挡住」= 传递地依赖某个环成员。按层升序推进即可:Kahn 保证依赖方的层严格更小。\n const blockedComps = new Set<number>();\n for (const i of [...comps.keys()].sort((x, y) => levelOf.get(x)! - levelOf.get(y)!)) {\n if ([...compDeps.get(i)!].some((j) => cyclicComps.has(j) || blockedComps.has(j))) blockedComps.add(i);\n }\n\n const buckets = new Map<number, string[]>();\n for (const n of nodes) {\n if (cyclic.has(n)) continue; // 环成员不进 waves:塞进去等于假装定得出顺序\n const lv = levelOf.get(compOf.get(n)!)!;\n if (!buckets.has(lv)) buckets.set(lv, []);\n buckets.get(lv)!.push(n);\n }\n // 整层都是环成员时那一层不出现 —— 「这一波什么都不建」不是可执行的指令。\n // 🔴 代价是 waves[0] 未必零依赖(见 WORDING 里那条 blockedByCycles 的警告)。\n const waves = [...buckets.keys()].sort((a, b) => a - b).map((lv) => buckets.get(lv)!.sort());\n\n return {\n selfLoops: [...selfLoops].sort(),\n cycles,\n unordered,\n waves,\n blockedByCycles: nodes.filter((m) => !cyclic.has(m) && blockedComps.has(compOf.get(m)!)).sort(),\n excluded,\n outside,\n };\n}\n\n/** 把一次 `analyze` 压成四个数 —— `excludedEdges.impact` 的两侧都用它,口径必然一致。 */\nfunction impactOf(r: Analysis): Record<string, number> {\n return {\n cycles: r.cycles.length,\n cycleMembers: r.unordered.length,\n selfLoops: r.selfLoops.length,\n blockedByCycles: r.blockedByCycles.length,\n };\n}\n\nconst NO_META_NOTE =\n '这些模型在名册里、但**本轮没拿到 meta**(多为 HTTP 404 的 `*Index` 型隐藏模型,' +\n '详见 unavailable.json)。⇒ 它们的**出边不可知**:图里看着没有依赖,只是因为' +\n '我们没读到它们的字段。🔴 它们照样在 waves 里(删掉等于假装名册里没这个模型),' +\n '而 waves 里「真的无依赖」与「出边不可知」**同形** —— 照 waves[0] 先导它们,' +\n '可能导到一半才发现有依赖。要判断某个 wave 成员可不可信,' +\n '**先和这份名单取交集**。';\nconst NO_META_UNKNOWN_NOTE =\n '调用方**没告诉**本模块哪些模型拿到了 meta(`modelsWithMeta` 未提供)⇒ ' +\n '本轮无从判断有没有「出边不可知」的节点。`null` 是「不知道」,' +\n '**不是**「一个都没有」—— 后者是 `count: 0` + 空列表。';\n\n/**\n * 从引用边集算模型级依赖图。`edges` / `dangling` 是 `refEdges` 的原样输出。\n *\n * `modelsWithMeta`:**本轮真拿到 meta 的模型 key**。名册里而不在这批里的节点,\n * 它们的出边**不可知** —— 边集是从 metas 算出来的,没有 meta 就一条边都算不出来,\n * 于是它们在 `waves[0]` 里与「爬过且真的无依赖」**完全同形**(8087 实测:761 − 742 = 19 个)。\n * ⚠️ `undefined` = 调用方没告诉我,产物里如实写 `null`;给了就是对过账,缺的那些点名。\n *\n * 🔴 `dangling` **原样透传,一个字段都不解读**。特别是 `dangling[].toModel`:\n * `reason === 'malformed-target'` 那一档存的是原值的字符串化形态(工具合成的字符串),\n * **不是模型名** —— 拿它去查、或塞进节点集,会凭空造出一个叫 `{\"k\":1}` 的模型。\n * 权威判据是 `reason`,而本模块压根不需要判读它。\n */\nexport function buildTopology(\n edges: RefEdge[],\n dangling: DanglingRef[],\n modelKeys: string[],\n modelsWithMeta?: Iterable<string> | null,\n): Record<string, unknown> {\n const nodes = [...new Set(modelKeys)].sort();\n const nodeSet = new Set(nodes);\n\n const kept = analyze(edges, nodes, nodeSet, true);\n // 🔴 对照组**现算**,不写死数字:产物要论证「这次的裁剪值不值」,就得拿**这次的**数说话。\n // 写死任何一个租户的数,客户环境的 topology.json 都会拿别人的数来论证自己的裁剪。\n const withAudit = analyze(edges, nodes, nodeSet, false);\n\n let noMeta: Record<string, unknown>;\n if (modelsWithMeta === undefined || modelsWithMeta === null) {\n noMeta = { count: null, models: null, note: NO_META_UNKNOWN_NOTE };\n } else {\n const seen = new Set(modelsWithMeta);\n const missing = nodes.filter((n) => !seen.has(n));\n noMeta = { count: missing.length, models: missing, note: NO_META_NOTE };\n }\n\n return {\n crawled: true,\n wording: WORDING,\n nodeCount: nodes.length,\n // 🔴 「出边不可知」的节点。恒发键:缺席会被读成「这个工具还不懂这一档」,\n // 而 `null` 与 `[]` 又是两个不同的事实(见上方两条 note)。\n nodesWithoutMeta: noMeta,\n // 🔴 边总数按**传入的边**记,不按图里用掉的边记:两者的差额是\n // excludedEdges + edgesOutsideNodes + 自环,全部摆在同一份产物里才对得上账。\n edgeCount: edges.length,\n selfLoops: kept.selfLoops,\n cycles: kept.cycles,\n waves: kept.waves,\n unordered: kept.unordered,\n blockedByCycles: kept.blockedByCycles,\n excludedEdges: {\n count: kept.excluded.length,\n // 依据写成机器可读的一档,下游要放回去时不必去解析中文。\n basis: 'field-name-convention',\n fieldKeys: [...AUDIT_FIELD_KEYS],\n note: EXCLUDED_NOTE,\n impact: {\n withAudit: impactOf(withAudit),\n withoutAudit: impactOf(kept),\n note:\n '两组数都是**本次现算**的:同一份边集、同一段代码,' +\n '一次含审计边、一次排除,差额就是这条口径在**你手上这份数据**上的代价。' +\n 'reference 是另一个租户的参考值,不是本次结果。',\n reference: REFERENCE_IMPACT,\n },\n // 原样,键与 refEdges 产出的引用边逐字相同(七键)—— 放得回去\n edges: kept.excluded,\n },\n edgesOutsideNodes: {\n count: kept.outside.length,\n note: OUTSIDE_NOTE,\n edges: kept.outside, // 原样,同 excludedEdges 一个形状\n },\n dangling: [...dangling],\n note: NOTE,\n };\n}\n\nexport function writeTopology(layout: ReconLayout, topo: Record<string, unknown>): string {\n return layout.writeJson('topology.json', topo);\n}\n","/**\n * 每模型的可写性画像(契约清单 §3.6,33 条):字段能不能填、填什么类型、值从哪来、\n * 这个模型能不能写。\n *\n * 🔴 `mutation` 这一维如实记录 allowed/reason;`unknown` 留给**没查到**的五种情况\n * (见 `NOT_QUERIED` 上方那张表)。\n * 🔴 **别把这五种都说成「实测为 0」**:底账量到的只有「742 个模型全部有应答、0 失败」\n * 这一条,它覆盖的是**第一种**(没应答)。其余四种**没有实测数据**。\n * 它们与「允许」不许同名,与它们出现过几次无关。\n * ⚠️ 那张表说的是 `collectMutation` 这一侧;`buildMapping` 侧还有三种**调用方喂坏形状**的\n * 成因(见 `mutationSection`),这几种措辞两两不同名。\n *\n * 🔴 **不得**用 `isTree` / `multiple` / `treeLoadMode` 做任何判断:实测这三个键在全部\n * 14299 个字段上分别恒为 `false` / `false` / `\"lazy\"`,是序列化缺省而非信号。\n * 拿它们分支,另一侧夹具根本走不到,守卫看着有、实际是空的。\n *\n * 🔴 **本模块与 `topology` 对审计字段的处置故意不同,别「统一」掉**:\n * `createById` / `updateById` 占指向 `User` 的引用的 94%(1476 / 1564,出现在 738 个模型上)。\n * `topology` 把它们**排除出依赖图** —— 不排除时 742 个模型有 717 个(96%)定不出顺序,\n * 那份产物等于没用。而 mapping 是**字段清单**不是依赖图:它们照常在列,\n * 只在 `ref.auditBackref` 上打一个标记。两处的字段名从 `topology.AUDIT_FIELD_KEYS`\n * 同源取,不各存一份 —— 存两份必然漂移,而漂移的表现是两份产物对不上账、谁也说不清差在哪。\n */\nimport type { Outcome, ReconProbe } from '../probe';\nimport type { ReconLayout } from '../layout';\nimport { LayoutContainmentError } from '../layout';\nimport { sequentialGet } from './crawl';\nimport { metaFields } from './fields';\nimport type { RefEdge } from './refs';\n// 🔴 同源取审计字段名,不抄一份。见模块 docstring:两处处置不同,但**依据必须是同一个**。\nimport { AUDIT_FIELD_KEYS } from './topology';\n// 🔴 与编排层 / valuedomain 同一个键,不抄字面量。\nimport { KIT_CARRY_OVER_KEY } from './carry-over';\n\nconst AUDIT_SET = new Set<string>(AUDIT_FIELD_KEYS);\n\nexport const MUTATION_PATH = (model: string): string => `/api/models/${model}/mutation-capability`;\n\n/**\n * 🔴 `batch_delete` 是 snake_case —— 服务端就是这么返的(实测)。写成 `batchDelete`\n * 会让产物与服务端对不上,而这个手滑极其自然(产物里其余的键几乎都是驼峰)。\n */\nexport const MUTATION_OPS = ['create', 'update', 'delete', 'batch_delete'] as const;\nexport type MutationOp = (typeof MUTATION_OPS)[number];\n\n// `unknown` 有**五**个成因,措辞必须互不相同 —— 它们的下一步动作不一样:\n// · 没应答 → 去查这个模型为什么打不通;\n// · 缺某一项 → 去问服务端为什么少发这一项;\n// · allowed 不是布尔 → 去问服务端为什么发了个 null / \"yes\";\n// · 批次里没这一条 → 请求发出去了、结果没回到手上,重跑一轮即可;\n// · 本轮没查 → 是我们自己没查(定向重爬),补一轮就有。\n// 合并成同一句话,`unknown` 就同时兼职五种含义,顾问无从分流。\nconst NOT_QUERIED = '本轮没有查询该模型的 mutation-capability';\n/**\n * 并发带来的**第五种**成因。与「未应答」故意不同名:那一档是**收到了**一个不能用的响应\n * (有 status / error 可查),这一档是批次结果里压根没有这个模型 ——\n * 前者去查服务端与网关,后者重跑一轮就有。与「本轮没查」也不同名:那一档我们根本没发这个请求。\n *\n * 🔴 **可达性说准**:当前的生产探针对**每一个** path 都有落点,所以真实的批次抖动会落进\n * 「未应答(HTTP -1 / network)」,**不会**表现成「批次里没有这个模型」。\n * ⇒ 这一档是**客户端契约的兜底**:本函数只要求调用方给一个「path → Outcome 的映射」,\n * 不要求它对每个 path 都有键。fail-closed 不猜:结果没回来就是没查到,绝不缺省成「允许」。\n */\nconst BATCH_LOST =\n '已随并发批次发出,但批次结果里没有这个模型 —— 结果丢了,' +\n '既不是服务端拒答、也不是本轮没查,重跑一轮即可';\n// 🔴 下面这三句是**调用方**把形状喂坏了(不是服务端),措辞与**上面那几种**一个都不许同名。\n// 「传入的」与「响应里」是这两组的分水岭:前者去查是谁拼的这份 mutation,后者去查服务端。\nconst badSection = (t: string): string => `传入的 mutation 段不是对象(${t}),无法判读`;\nconst badCell = (op: string, t: string): string => `传入的 mutation 里 ${op} 这一项不是对象(${t}),无法判读`;\nconst missingCell = (op: string): string => `传入的 mutation 里没有 ${op} 这一项`;\n\nexport const WORDING =\n 'writable=false 的两种原因:derived:<kind>(派生字段,服务端算出来的)、' +\n 'readonly(元数据声明只读)。autoGenerate=true 的字段(工号 / 组织 / 岗位 / 法人编码等)' +\n '**不是**不可写:服务端只在字段为空时生成,调用方手工指定的值优先 —— 由独立标记 ' +\n '`autoGeneratedIfAbsent` 点名(不送则生成、送则采纳),不再进 notWritableReason。' +\n '🔴 writable=true **只表示元数据没声明这两种不可写**,不表示这个字段该由顾问填 —— ' +\n '实测 createById / updateById 这 1476 个审计字段四个标志全是 false,照样是 writable=true,' +\n '它们由 ref.auditBackref=true 点名(topology 把同一批边排除出依赖图,这里不排除:' +\n 'mapping 是字段清单,不是依赖图)。' +\n 'ref.valueDomain 只在目标是码表(DYNAMIC / CO_*)时给出;指向业务模型时为 null —— ' +\n // 🔴 这段拆分**必须自己对得上账**:原文写「234 个引用目标里只有 32 个是码表,其余 200 个」\n // —— 32 + 200 = 232 ≠ 234,产物里唯一解释「为什么这个 ref 没有 valueDomain」的那段话\n // 自己少了 2 个目标。判据从措辞里抠数做加法,不背数字。\n '实测 234 个声明出来的引用目标里,230 个在名册中:其中 32 个是码表、198 个是业务模型' +\n '(User 1564 / OrgUnit 292 / Employee 199 …)要按业务键现查;另有 4 个悬空目标' +\n '(声明了引用但不在 /api/system/models 名册里,见 topology.json 的 dangling)。' +\n 'valuedomain 里没有业务模型的对照表,指一个不存在的文件比不指更糟:' +\n '顾问会以为那份对照表只是还没生成。' +\n 'ref.labelFieldSource=unknown 表示产品没声明显示字段名' +\n '(实测进图的 2923 条边里 1366 条没声明),**不是**「默认用 name」—— ' +\n '那 1366 条指向 224 个在册目标,其中 60 个(27%,目标模型口径)连 name 字段都没有,' +\n '按边算 152 条(11%,字段口径)落在它们身上。' +\n 'mutation.allowed 恒为三个值之一:true / false / \"unknown\"。' +\n '\"unknown\" 表示该模型这一项没查到,**不是**「允许」;具体是哪一种没查到看 reason' +\n '(服务端侧:没应答 / 响应缺这一项 / allowed 不是布尔;工具侧:并发批次里没拿到这个模型的结果;' +\n '调用方侧:本轮没查 / 传入的 mutation 段不是对象 / 传入的这一项不是对象 / 传入的里没有这一项 —— ' +\n '八种成因措辞各不相同,「传入的」开头的是拼这份基线的人搞错了,不是服务端)。' +\n 'enumCount 数的是该字段候选值的**元素个数**,与 index/fields.jsonl 里该字段的 enums 行数相等。';\n\n/**\n * mutation 段里的一格。\n *\n * 🔴 `allowed` 是**可选的**:`mutationSection` 会把调用方给的对象**原样透传**\n * (取值归一是 `collectMutation` 的职责,两处都归一就会分叉)——\n * 而一个手拼的 `{reason: \"...\"}` 里压根没有 `allowed` 键。写成必填会逼出一个\n * `as unknown as` 断言,那等于把「这里可能没有这个键」这件事从类型里抹掉,\n * 而 `_index.json` 的补集判据正是为了接住它才写成补集的。\n */\nexport interface MutationCell {\n allowed?: unknown;\n reason?: unknown;\n}\nexport type MutationSection = Record<string, MutationCell>;\nexport type MutationMap = Record<string, MutationSection>;\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\nfunction typeName(v: unknown): string {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'list';\n return typeof v;\n}\n\n/**\n * → `{mutation, problems}`,`mutation[modelKey][op] = {allowed, reason}`。\n *\n * 🔴 **走 `getMany` 并发**,形态与 `collectActionGates` 逐字同款:`path → model` 的映射\n * 把「哪个模型的哪一项没查到」这个归属重新建起来,并**必须**处理「批次里没有返回该 path」\n * 那一档 —— 见 `BATCH_LOST`。\n *\n * 去重后再查:重复的 modelKey 会白打一次请求,并让同一个问题在 `problems` 里记两遍,\n * 下游按条数报「有 2 个问题」就是虚报。\n * 🔴 `paths` 按**排序后**的 key 构建 ⇒ 返回的 `mutation` 仍是排序序:产物可 diff\n * 这件事不能因为换了取数方式就丢。\n */\nexport async function collectMutation(\n probe: ReconProbe,\n modelKeys: string[],\n): Promise<{ mutation: MutationMap; problems: string[] }> {\n const keys = [...new Set(modelKeys)].sort();\n const paths = new Map<string, string>();\n for (const k of keys) paths.set(MUTATION_PATH(k), k);\n const results: Record<string, Outcome> = probe.getMany\n ? await probe.getMany([...paths.keys()])\n : await sequentialGet(probe, [...paths.keys()]);\n\n const mutation: MutationMap = {};\n const problems: string[] = [];\n for (const [path, key] of paths) {\n const out = results[path];\n if (out === undefined) {\n // 批次里没有这个模型:客户端契约的兜底。fail-closed。\n problems.push(`${key}: mutation-capability ${BATCH_LOST}`);\n mutation[key] = Object.fromEntries(\n MUTATION_OPS.map((op) => [op, { allowed: 'unknown', reason: BATCH_LOST }]),\n );\n continue;\n }\n const body = out.error === 'ok' ? out.body : null;\n if (!isRecord(body)) {\n // 🔴 HTTP 200 但载荷不是对象(数组 / 字符串 / null)与 500 同档:我们**不知道**\n // 这个模型能不能写。乐观缺省成 allowed=true 会让脚本直接开写,\n // 然后在客户环境里撞一堵墙 —— 而那时已经写进去半份数据了。\n const reason = `未应答(HTTP ${out.status} / ${out.error})`;\n problems.push(`${key}: mutation-capability ${reason}`);\n mutation[key] = Object.fromEntries(MUTATION_OPS.map((op) => [op, { allowed: 'unknown', reason }]));\n continue;\n }\n const entry: MutationSection = {};\n for (const op of MUTATION_OPS) {\n const cell = body[op];\n // 🔴 判据是「有没有 allowed 这个**键**」,不是「allowed 真不真」。这一支与下面\n // 那道归一都会产出 unknown,但**措辞不同名**、指向的下一步也不同:这里是\n // 「服务端少发了这一项 / 这一项压根不是对象」,下面是「发了,但值不是布尔」。\n if (!isRecord(cell) || !('allowed' in cell)) {\n entry[op] = { allowed: 'unknown', reason: `响应里没有 ${op} 这一项` };\n problems.push(`${key}: mutation-capability 响应缺 ${op}`);\n continue;\n }\n const allowed = cell.allowed;\n // 🔴 `allowed` 归一到**三个值**:true / false / \"unknown\"。非布尔一律归 unknown,\n // 而不是原样带出去。这不是洁癖,是**让下游的判据能自证前提**:`_index.json` 用\n // `=== false` 认「被拒」、用补集认「没查到」,原样带出去的第四种取值会两边都不落 ——\n // 于是它在索引里彻底隐身,只有逐个打开 742 份 sheet 才看得见。\n if (typeof allowed !== 'boolean') {\n entry[op] = { allowed: 'unknown', reason: `allowed 不是布尔(${JSON.stringify(allowed)}),无法判读` };\n problems.push(`${key}: mutation-capability 的 ${op} 项 allowed 不是布尔(${JSON.stringify(allowed)})`);\n continue;\n }\n entry[op] = { allowed, reason: cell.reason ?? null };\n }\n mutation[key] = entry;\n }\n return { mutation, problems };\n}\n\n/**\n * 把**调用方传进来的**某个模型的 mutation 段整成「四个 op、每个都是对象」的形状。\n *\n * 🔴 这道闸原先只装在 `collectMutation` 那一侧,`buildMapping` 这一侧没有。\n * 而不经 `collectMutation` 的调用方是真实存在的:手拼、断点续跑从磁盘读回、\n * 定向重爬合并两轮结果。实测的后果不是「记漏一档」,是从落盘那一步抛出一条\n * 无源码可读的栈 —— 正是本模块别处一直在防的那种。\n *\n * 🔴 形状归形状,**取值不动**:cell 是对象就原样透传,哪怕 `allowed` 是 null / \"yes\"。\n * 取值的归一是 `collectMutation` 的职责,这里再归一一次,两处判读就又分叉了;\n * 而未归一的取值有 `_index.json` 的补集判据兜着(它对任何取值都自证)。\n */\nfunction mutationSection(entry: unknown): MutationSection {\n if (entry === undefined || entry === null) {\n // 「本轮没查这个模型」——与「查了、但形状坏」不是一回事。\n return Object.fromEntries(MUTATION_OPS.map((op) => [op, { allowed: 'unknown', reason: NOT_QUERIED }]));\n }\n if (!isRecord(entry)) {\n const reason = badSection(typeName(entry));\n return Object.fromEntries(MUTATION_OPS.map((op) => [op, { allowed: 'unknown', reason }]));\n }\n const section: MutationSection = {};\n for (const op of MUTATION_OPS) {\n const cell = entry[op];\n if (isRecord(cell)) {\n section[op] = cell;\n } else if (op in entry) {\n section[op] = { allowed: 'unknown', reason: badCell(op, typeName(cell)) };\n } else {\n // 补齐缺的 op:sheet 的 mutation 段恒有四项,下游取 `sheet.mutation[op]` 不会落空。\n section[op] = { allowed: 'unknown', reason: missingCell(op) };\n }\n }\n return section;\n}\n\n/**\n * → `{writable, reason}`。\n *\n * 🔴 判据只用**观测到有区分度**的键。`isTree` / `multiple` / `treeLoadMode`\n * 在实测的 14299 个字段上分别恒为 false / false / \"lazy\" —— 拿它们分支等于写一条\n * 夹具永远走不到的死路。\n *\n * 🔴 顺序即优先级:`derived` 排第一,因为它是**唯一带成因**的那一档\n * (`derived:aggregate` / `derived:expression`,实测 55 个字段,分布 40 / 15)。\n * 两种成因同时成立时先报最有解释力的那个 —— 顾问看到 `readonly` 只会去问\n * 「谁把它设成只读的」,看到 `derived:expression` 才知道这个值是算出来的、根本没有「填」这个动作。\n *\n * 🔴 `autoGenerate` **不是**不可写。产品契约是「**仅在字段为空时生成;调用方手工指定的值优先**」\n * —— 主数据(工号 / 组织编码 / 岗位编码)的导入靠的就是这档语义。把它标成 `writable:false`\n * 会让按元数据编程的调用方不发 `code`,于是业务键被服务端流水号静默替换。\n * 因此这里只给一个**新标记** `autoGeneratedIfAbsent`,不再进 `notWritableReason`。\n */\nfunction writability(field: Record<string, unknown>): { writable: boolean; reason: string | null } {\n const derived = field.derived;\n if (derived) {\n // 形状按实测是对象(`{kind:\"expression\", expression:\"AGE(birthDate)\"}`),\n // 但不许按固定形状解包:只取 kind,别的忽略;不是对象时记 unknown 而不是崩。\n const kind = isRecord(derived) ? derived.kind : null;\n return { writable: false, reason: `derived:${typeof kind === 'string' && kind ? kind : 'unknown'}` };\n }\n if (field.isReadonly) return { writable: false, reason: 'readonly' };\n return { writable: true, reason: null };\n}\n\nexport interface MappingSheet {\n model: string;\n crawled: true;\n wording: string;\n mutation: MutationSection;\n fieldCount: number;\n fieldsProblem: string | null;\n fields: Array<Record<string, unknown>>;\n}\n\n/**\n * → 每模型一份 sheet,按 modelKey 排序。\n *\n * `edges` 是 `refEdges` 的原样输出(七键),本模块**不自己判读** `object` / `refer`:\n * 两处各判一次就会长出「一边算 2929 条、另一边算 2926 条」这种谁也说不清的差额。\n *\n * `codeTables` 是**码表模型 key 的集合**。只有目标落在这个集合里时才给 `ref.valueDomain`\n * —— 指向一个不存在的对照表文件比不指更糟。缺省是空集合:没传就一个都不指,\n * **不会**凭前缀猜(`CO_` 前缀是某个租户的观测,不是产品契约)。\n *\n * 🔴 排序是产物可 diff 的前提:`metas` 的键序**不恒定**(定向重爬与全量爬出来的顺序不同)。\n */\nexport function buildMapping(\n metas: Record<string, unknown>,\n edges: RefEdge[],\n mutation: unknown,\n codeTables: Iterable<string> = [],\n): MappingSheet[] {\n const codeSet = new Set(codeTables);\n const byField = new Map<string, RefEdge>();\n for (const e of edges) byField.set(`${e.fromModel} ${e.fromField}`, e);\n\n const sheets: MappingSheet[] = [];\n for (const modelKey of Object.keys(metas).sort()) {\n // 判读走 `metaFields` 单一口径 —— 与 `extractFieldRows` / `refEdges` 同源。\n // 🔴 **problem 必须接住,不许丢掉**:丢掉之后「载荷是 `{fields: null}` / 根本不是对象」\n // 这种**爬过、但判不出字段**的第三态,产出 `{crawled: true, fieldCount: 0, fields: []}`\n // —— 与「爬过且确实没字段」**逐字相同**。\n // 台账落在**本 sheet 自己**的 `fieldsProblem` 上,与 `extractFieldRows` 的 `dropped`\n // 不是同一个落点:那份台账在**另一份产物**里,顾问打开 `mapping/X.json` 看到 0 个字段时,\n // 不该还得知道去别处翻。计数**不**重复:那边记的是行数,这里记的是一句人话。\n const { fields, problem } = metaFields(metas[modelKey]);\n const rows: Array<Record<string, unknown>> = [];\n for (const f of fields) {\n if (!isRecord(f)) continue;\n const key = f.key;\n // 🔴 key 纪律必须与 `extractFieldRows` 里「没有可用的 key」那一支逐字一致 ——\n // **非字符串的 key 也要丢**。只判真假会让 `key: 7` 活着进 mapping,于是:\n // ① 同一个字段在 index/fields.jsonl 里被丢进 dropped、在 mapping 里却存在,\n // 两个消费者对同一份输入各判各的;\n // ② 下游一排序就类型错 —— 客户现场一条无源码可读的栈。\n if (typeof key !== 'string' || !key) continue;\n const { writable, reason } = writability(f);\n const edge = byField.get(`${modelKey} ${key}`);\n const ref = edge\n ? {\n toModel: edge.toModel,\n carrier: edge.carrier,\n // labelField / labelFieldSource 原样透传:没声明就是 null + \"unknown\",\n // 不许在这里兜底成 \"name\"(实测 60 个目标模型连 name 字段都没有)。\n labelField: edge.labelField,\n labelFieldSource: edge.labelFieldSource,\n valueDomain: codeSet.has(edge.toModel) ? `valuedomain/${edge.toModel}.json` : null,\n // 依据只有**字段名约定**,与 topology 同源、口径也同 —— 不是产品声明:\n // 实测这 1476 个字段的 isReadonly/autoGenerate/isList/isInfo 全为 false。\n auditBackref: AUDIT_SET.has(key),\n }\n : null;\n const enums = f.enums;\n rows.push({\n key,\n // 🔴 中文名的键是 `name`,**不是 `label`**;必填的键是 `isRequired`,**不是 `required`**。\n // 接错不报错也不崩,产物里只是「全是英文键名」或「一个字段都不必填」。\n name: f.name ?? '',\n type: f.type ?? '',\n required: Boolean(f.isRequired),\n writable,\n notWritableReason: reason,\n // 🔴 autoGenerate=true 的字段**可以**由调用方指定 —— 服务端只在字段为空时才生成。\n // 这是 writable=true 之外的一道**独立标记**,避免把编码当成只读而漏发、导致业务键漂移。\n autoGeneratedIfAbsent: Boolean(f.autoGenerate),\n ref,\n // 🔴 数**全部元素**,不是「形状认得出的那些」。`fields` 侧对非对象元素也逐个产出一行\n // (带 `_kitValueSource` 认领),这里若只数对象,一个 `enums: [\"A\",\"B\"]` 的字段\n // 在 mapping 里就是 enumCount=0 —— 与「这个字段没有候选值」同形,\n // 而 index/fields.jsonl 里明明有两行。\n enumCount: Array.isArray(enums) ? enums.length : 0,\n });\n }\n sheets.push({\n model: modelKey,\n // 「爬过且这个模型确实没有字段」必须与「没爬过」(文件不存在)区分开。\n crawled: true,\n wording: WORDING,\n mutation: mutationSection(isRecord(mutation) ? mutation[modelKey] : mutation),\n fieldCount: rows.length,\n // 🔴 `null` = 载荷判读正常(fieldCount=0 就是「这个模型确实没字段」);\n // 非 null = **爬过、但判不出字段**,此时 fieldCount=0 不代表「没字段」。\n fieldsProblem: problem ?? null,\n fields: rows,\n });\n }\n return sheets;\n}\n\n/**\n * 取某份 sheet 里某个 op 的 `allowed`;形状不对时返回 `'unknown'` 而**不是**抛。\n *\n * 🔴 `buildMapping` 出来的 sheet 形状已由 `mutationSection` 保住,所以这一支\n * 只对**手拼 sheets 直调 `writeMapping`** 的调用方生效。留着它,是为了让\n * 「划分对任何调用方都自证」这句话对**形状**也成立、而不只是对取值域成立 ——\n * 一份坏形状的 sheet 该让它落进 unknown 被点名,不该让整份索引以一条栈收场。\n */\nfunction allowedIn(sheet: Record<string, unknown>, op: string): unknown {\n const section = sheet.mutation;\n const cell = isRecord(section) ? section[op] : undefined;\n return isRecord(cell) ? cell.allowed : 'unknown';\n}\n\nconst PATH_SEPARATORS = ['/', String.fromCharCode(92)];\n\n/**\n * modelKey 能不能安全地当文件名用 → 不能则给出人话理由。\n *\n * 🔴 **落盘闸守的是产物根,不是 `mapping/` 这一层**,所以它接不住这一档:\n * `mapping/../evil.json` 解析后仍在根里,闸不响,文件却落在 `models.json` 旁边,\n * 既按 modelKey grep 不回去、又与别的产物混在一起。\n * ⇒ 本模块自己判「这个键能不能当一段文件名」,落盘闸留作最后一道。\n *\n * 拒绝而不是消毒:消毒后文件名不再等于 modelKey,顾问 `ls mapping/ | grep Employee` 就对不上了。\n */\nexport function unsafeKeyReason(modelKey: unknown): string | null {\n // ⚠️ 这一支**守不到主路径**:走正常管道时 `buildMapping` 里的排序会先对非字符串 key 出问题。\n // 它是给**直调 `writeMapping` 的调用方**(手拼 sheets、从磁盘读回 sheet 再落盘)留的。\n if (typeof modelKey !== 'string' || !modelKey) return `modelKey 不是非空字符串(${JSON.stringify(modelKey)})`;\n if (PATH_SEPARATORS.some((s) => modelKey.includes(s))) {\n return `modelKey 含路径分隔符,不能当文件名:${JSON.stringify(modelKey)}`;\n }\n if (modelKey === '.' || modelKey === '..') return `modelKey 指向目录本身或上级:${JSON.stringify(modelKey)}`;\n // 🔴 与 valuedomain 的落盘判据**对齐**。此前两侧不一致,实测两个后果,都是「产物在说假话」:\n // · 前导 `.`:`mapping/.hidden.json` **写得出来** —— `ls mapping/` 看不见它,\n // 它却同时计进 modelCount **和** sheetsWritten,于是 `sheetsNotWritten === 0` 在声称\n // 「一份都没漏」。而同一个 key 在 valuedomain 那边是被拒的。\n // · 前导 `_`:`_index` 是本目录**工具自己的保留文件名**。一张叫 `_index` 的表会把\n // `mapping/_index.json` 顶掉 —— 顶掉的正是这份索引本身,而索引里的 sheetsWritten\n // 还在自称一切正常。\n if (modelKey.startsWith('.') || modelKey.startsWith('_')) {\n return (\n `modelKey 以 . 或 _ 开头,不能当文件名:${JSON.stringify(modelKey)} —— ` +\n '前者产出 ls 看不见的隐藏文件、后者会顶掉工具自己的 _index.json'\n );\n }\n return null;\n}\n\nfunction reasonTally(sheets: MappingSheet[]): Record<string, number> {\n const tally = new Map<string, number>();\n for (const sheet of sheets) {\n for (const f of sheet.fields) {\n const r = f.notWritableReason;\n if (typeof r === 'string' && r) tally.set(r, (tally.get(r) ?? 0) + 1);\n }\n }\n return Object.fromEntries([...tally.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));\n}\n\nexport interface WriteMappingOptions {\n problems?: string[] | null;\n danglingValueDomain?: unknown[] | null;\n carryOver?: unknown;\n roundId?: string | null;\n}\n\n/**\n * 一模型一文件 + 一份索引。返回写出的路径,顺序与 `sheets` 一致、`_index.json` 在最后。\n *\n * 一模型一文件是为了让顾问能 `cat mapping/Employee.json` 直接看 —— 742 个模型挤进一个\n * 文件时,那份文件在客户现场既 grep 不动也读不完。\n *\n * 🔴 **一个写不下来的 modelKey 不许掀掉整份产物**:抛出去的话,前面几百个模型写了、\n * 后面的没写,而磁盘上看不出是在哪一步断的。转成 `_index.json` 里的 `unsafeModelKeys`\n * 显式条目 —— 「没写下来」与「这个模型不存在」在产物里必须长得不一样。\n *\n * 🔴 后四项都是**台账,不是装饰**。它们此前只进 CLI 摘要,而摘要跑完就没了 ——\n * **产物是被单独拷走的那一半**。一律 **`null` = 调用方没告诉我**、\n * **`[]` / 具体值 = 对过账**。键**恒发**,缺席不许兼职「没有」。\n */\nexport function writeMapping(\n layout: ReconLayout,\n sheets: MappingSheet[],\n opts: WriteMappingOptions = {},\n): string[] {\n const written: string[] = [];\n const unsafe: Array<Record<string, unknown>> = [];\n for (const sheet of sheets) {\n const modelKey: unknown = sheet.model;\n let reason = unsafeKeyReason(modelKey);\n if (reason === null) {\n try {\n written.push(layout.writeJson(`mapping/${modelKey as string}.json`, sheet));\n continue;\n } catch (err) {\n // 落盘的越界闸(产物根那一层)—— 上面的判据没想到的形态由它兜底。\n //\n // ⚠️ **只接越界这一档,别的照旧往上抛**:磁盘满 / 只读挂载这类是**系统性**故障,\n // 逐条吞掉只会产出一份处处是窟窿的基线。\n // 🔴 但这条理由有个**例外,如实记在这里**:超长 modelKey(文件名 > 255 字节)\n // 触发的 ENAMETOOLONG 是**逐 key 的**、不是系统性的,它仍会掀掉整批。\n // 没有改,因为 742 个真实 modelKey 全都很短;真撞上了,修法是在\n // `unsafeKeyReason` 里加一条长度判据。\n if (!(err instanceof LayoutContainmentError)) throw err;\n reason = (err as Error).message;\n }\n }\n unsafe.push({\n model: typeof modelKey === 'string' ? modelKey : JSON.stringify(modelKey),\n reason,\n fieldCount: sheet.fieldCount,\n });\n }\n written.push(\n layout.writeJson('mapping/_index.json', {\n crawled: true,\n wording: WORDING,\n // 🔴 轮次身份。一轮 recon 被打断时,**这一份最容易留在上一轮**(它是编排里最后写的\n // 几份之一)—— 而那时 `mapping/` 里缺某个模型,与「那个模型的 modelKey 不安全、\n // 被拒写」同形。`null` = 调用方没告诉我,恒发键。\n roundId: opts.roundId ?? null,\n // 🔴 三份台账**恒发键**(null = 调用方没告诉我,[] = 对过账且一条都没有)。\n // 明细与摘要两处都要有:只进摘要 ⇒ 产物被单独拷走后就没了;只进产物 ⇒\n // 跑完那一刻看不见。少任何一处都等于没记。\n problems: opts.problems ?? null,\n danglingValueDomainPointers: opts.danglingValueDomain ?? null,\n [KIT_CARRY_OVER_KEY]: opts.carryOver ?? null,\n modelCount: sheets.length,\n // 🔴 「有几个模型」与「有几份 sheet 文件」不是同一个数,差额就是下面这一档 ——\n // 不点名的话,`ls mapping/ | wc -l` 与 modelCount 对不上而没人说得清为什么。\n sheetsWritten: written.length,\n unsafeModelKeys: unsafe,\n notWritableByReason: reasonTally(sheets),\n // 🔴 判据是 `=== false`,与下面 `mutationUnknown` 的补集判据**配成一个划分**。\n // 它的牙来自那条**没经过归一的裸 null** 夹具:`!allowed` 对 null 为真,\n // 于是同一个模型会同时落进 denied 和 unknown,划分不变量当场把它逮住。\n mutationDenied: Object.fromEntries(\n MUTATION_OPS.map((op) => [\n op,\n sheets\n .filter((s) => allowedIn(s as unknown as Record<string, unknown>, op) === false)\n .map((s) => s.model)\n .sort(),\n ]),\n ),\n // 与 mutationDenied 同一批数据的另一面:没查到的那些必须也点名,\n // 否则「不在 denied 里」会被读成「允许」。\n //\n // 🔴 判据是**补集**(既不是 true 也不是 false),不是 `=== 'unknown'`。\n // 差别只在一种输入上,而那种输入是真实可达的:`collectMutation` 归一过的 mutation 里\n // 只有三值,两种写法等价;但 `writeMapping` **不检查这个前提**,而喂给它的 mutation\n // 完全可能没经过归一 —— 手拼、断点续跑从磁盘读回、定向重爬合并两轮结果,都是这条路。\n // 那时一个 `{allowed: null}` 在 `=== 'unknown'` 的写法下两个清单都不进,**在索引里隐身**。\n // 写成补集,划分在**取值域**上对任何调用方都自证。\n // 🔴 措辞收窄过一次:补集只管**取值**,管不了 **cell 的形状** —— 未归一的调用方喂\n // `{create: \"yes\"}`(cell 不是对象)时原先直接抛。形状那一半由 `mutationSection`\n // (build 侧)与 `allowedIn`(本函数侧)两道闸补上,两半齐了「对任何调用方都自证」才是句实话。\n mutationUnknown: Object.fromEntries(\n MUTATION_OPS.map((op) => [\n op,\n sheets\n .filter((s) => {\n const a = allowedIn(s as unknown as Record<string, unknown>, op);\n return a !== true && a !== false;\n })\n .map((s) => s.model)\n .sort(),\n ]),\n ),\n }),\n );\n return written;\n}\n","/**\n * `index/models.md`:给人读的一页式模型摘要(一模型一节,契约清单 §3.4,17 条)。\n *\n * 它不是 `fields.jsonl` 的替代品——检索走那份 JSONL,这份负责「顾问翻一眼就知道\n * 这个环境有什么、哪些动作是声明只读的、哪些格子根本没爬到、哪些东西没能进索引」。\n *\n * 🔴 整节只有一条主线:**「不知道」必须渲染成「(未声明)/ 未爬取 / 无法判读」,\n * 永远不许出现 `null`/`undefined`,更不许把「没爬到」渲染成「字段数:0」**。\n * 这是整份产物给顾问看的那一面,把未知渲染成 0 会直接把人导向错误结论。\n */\nimport { modelEntries } from '../fingerprint';\n// 🔴 判读 meta 载荷走 `metaFields` 这**一个**口径:本模块与 fields 吃的是同一份 metas\n// (runner 一次爬取喂给两边)。各写各的判读 = 一边记台账继续跑、另一边裸抛,\n// 而这种半容忍只在客户现场那一次暴露。\nimport { metaFields } from './fields';\n\n/**\n * 🔴 口径写死在产物里:闸的覆盖面永远是 state-scoped。\n * 产品没有「列出可用 state」的端点,「全集」不是一个可兑现的词——\n * 说成 full 就是在客户现场发一份自己都兑现不了的承诺。\n */\nconst SCOPE_NOTE =\n '闸的覆盖面是 **state-scoped**:产品里 state 不可枚举,' +\n '本文件只反映本次爬取所用的那一个 state。页签类问题必先问 state,不要信 base meta。';\n\n/** 无闸模型必须有这一句。省掉它,「没有只读闸」与「这一节没渲染出来」在产物里同形。 */\nconst NO_GATE =\n '- 声明只读的动作:**未发现声明只读的动作**(未声明 = 按写动作对待,无论它叫 preview 还是 check)';\n\n/**\n * 「meta 没爬到」与「这个模型没有字段」是两个值:实测 761 个模型里 19 个 meta 恒 404,\n * 渲染成「字段数:0」会让读的人得出一个错误结论。\n */\nconst FIELDS_NOT_CRAWLED = '- 字段数:未爬取(该模型 meta 不可用,详见 unavailable.json)';\n\n/** 缺值兜底。产物里出现 `null` 会被当成真的取值去搜——缺声明就明说缺声明。 */\nconst UNDECLARED = '(未声明)';\n\nconst ORPHAN_TITLE = '## 未归属的只读闸';\n\nexport interface GateRow {\n model?: string;\n key?: string;\n /**\n * 🔴 取值域含 **`null`**,不是只有 `undefined`:`gates.ts` 对缺失的 label\n * **显式兜成 `null`** —— 留 `undefined` 会被 `JSON.stringify` 把整个键删掉,\n * 闸条目就从 9 键变 8 键。两边的取值域必须对得上,否则这两份移植互相矛盾,\n * 而矛盾只会在编排层把 `Gate[]` 喂进来的那一刻才暴露(实测正是如此)。\n * 渲染侧 `gate.label || ''` 两态同兜,行为上没有第二档。\n */\n label?: string | null;\n source?: string[];\n guard?: string;\n}\n\nfunction gateDesc(gate: GateRow): string {\n // action 级中文名是 `label`(meta 的**字段**级叫 `name`,两者不同源,别统一)。\n // 它是顾问唯一能按**中文动作名** grep 到只读动作的落点;\n // 缺失时省掉这一截,而不是把 `null` 渲染进产物冒充一个动作名。\n const label = gate.label || '';\n const named = `\\`${gate.key}\\`` + (label ? ` · ${label}` : '');\n const source = (gate.source ?? []).join('+') || UNDECLARED;\n // guard 的前提由 gates 那一层保住(恒有值),但**别的喂法保不住**——缺就明说缺。\n const guard = gate.guard || UNDECLARED;\n return `${named}(source=${source},guard=${guard})`;\n}\n\n/**\n * `dropped` 是 `extractFieldRows` 的第二个返回值。\n *\n * 它必须在这里有**看得见的落点**:只被 runner 记进摘要还不够——产物是会被单独拷走的文件,\n * 「有 N 个字段没能进索引」这件事不写在纸面上,就与「产品里根本没有这些字段」同形。\n */\nexport function renderModelsMd(\n modelsBody: Record<string, unknown>,\n metas: Record<string, unknown>,\n gates: GateRow[],\n dropped: readonly string[] = [],\n): string {\n const byModel = new Map<string | undefined, GateRow[]>();\n for (const g of gates) {\n const list = byModel.get(g.model) ?? [];\n list.push(g);\n byModel.set(g.model, list);\n }\n\n const parts: string[] = [\n '# 模型索引',\n '',\n `共 ${modelsBody.totalCount ?? 0} 个模型;类型分布 ${JSON.stringify(modelsBody.typeCounts ?? {})}。`,\n '',\n SCOPE_NOTE,\n '',\n '字段一行一条在 `index/fields.jsonl`(按中文名 / 同义词 grep 即可反查 model + key)。',\n '',\n ];\n\n if (dropped.length > 0) {\n parts.push(\n `> ⚠️ 有 **${dropped.length}** 个字段没能进 \\`index/fields.jsonl\\`,` +\n '逐条如下(它们在索引里与「产品没有这些字段」同形,所以必须在这里点名):',\n );\n for (const d of dropped) parts.push(`> - ${d}`);\n parts.push('');\n }\n\n const listed = new Set<string>();\n // 🔴 `models[]` 里混进一个非对象,这里就会当场抛,且发生在**已经爬完、\n // 正要落最后一份产物**的时候——前面所有网络工作全部白费。\n // 走同一道门 `modelEntries()`:它只放对象型条目过。\n for (const m of modelEntries(modelsBody)) {\n const key = String(m.modelKey ?? '');\n listed.add(key);\n // 🔴 取名字必须容缺:实测 761 个 modelKey 里只有 739 个带 name。\n // 缺名字时不缀分隔符,也绝不渲染 `null`——产物里的 null 会被当成真的模型名去搜。\n const name = m.name || '';\n parts.push(name ? `## ${key} · ${name}` : `## ${key}`);\n parts.push('');\n parts.push(`- 类型:${m.type || UNDECLARED}`);\n if (!Object.prototype.hasOwnProperty.call(metas, key)) {\n parts.push(FIELDS_NOT_CRAWLED);\n } else {\n const { fields, problem } = metaFields(metas[key]);\n // 三个值不许糊成一个:没爬到 / 爬到了但载荷判读不了 / 爬到了确实是 N 个字段。\n parts.push(\n problem === undefined\n ? `- 字段数:${fields.length}`\n : `- 字段数:无法判读(${problem}),该模型没有字段进索引`,\n );\n }\n const gs = byModel.get(key) ?? [];\n if (gs.length > 0) {\n parts.push('- 声明只读的动作:');\n for (const g of gs) parts.push(` - ${gateDesc(g)}`);\n } else {\n parts.push(NO_GATE);\n }\n parts.push('');\n }\n\n // 🔴 模型清单里没有的闸**不许蒸发**。触发条件是真的:清单响应形状异常时会回落成空清单,\n // 那一刻**全部**闸都会变成孤儿——而 models.md 是顾问唯一能按中文动作名 grep 到只读闸的落点,\n // 丢一条不留痕就与「这条闸不存在」同形。\n const orphans = gates.filter((g) => !listed.has(String(g.model ?? '')));\n if (orphans.length > 0) {\n parts.push(ORPHAN_TITLE);\n parts.push('');\n parts.push(\n `以下 **${orphans.length}** 条闸的模型不在本轮的模型清单内 —— ` +\n '**为什么不在,本模块没有查过**。🔴 **别读成「发现了隐藏模型」**。' +\n '已知**至少**两条路长成这样(不是全集):① 模型清单本身没取全' +\n '(清单响应形状异常时会回落成空清单);② 这几条闸是**沿用上一轮产物**的,' +\n '而那一轮的名册与本轮不同(见 `gates.json` 的 `coverage._kitCarryOver`)。' +\n '先看 `models.json` 的 `_kitPremiseCheck`。',\n );\n parts.push('');\n // 缺 model 的 gate 走「(未声明)」兜底:产物里的 null 会被当成真的模型名去搜——\n // 与本文件「绝不渲染 null」是同一条纪律。\n for (const g of orphans) parts.push(`- \\`${g.model || UNDECLARED}\\` · ${gateDesc(g)}`);\n parts.push('');\n }\n\n return parts.join('\\n');\n}\n\nexport function writeModelsMd(\n layout: { writeText(rel: string, text: string): string },\n text: string,\n): string {\n return layout.writeText('index/models.md', text);\n}\n","/**\n * `ref.valueDomain` 悬空指针的**第二条独立证据链**。\n *\n * 🔴 存在理由是 `mapping.ts` 自己的 WORDING:「指一个不存在的文件比不指更糟:顾问会\n * 以为那份对照表只是还没生成」——那句话把悬空判成了不可接受的失败,却没有任何检测。\n * 实测得出的三个悬空来源:\n * ① **两侧路径判据不对称**:`valuedomain` 拒 `/` `\\` **与前导 `.`**,\n * `mapping.unsafeKeyReason` 不拒前导 `.` ⇒ `.hidden` 那张表的 sheet 写得出来,\n * 它指向的对照表却被拒了;\n * ② `valuedomain` 落盘撞上 layout 的越界闸(只进 writeProblems,文件没写);\n * ③ **口径差**:「名册里是 DYNAMIC」与「对照表真的写出来了」是两个集合。\n * ③ 已在编排里根治(`codeTableFiles` 取自 `writeValuedomain` 真写出来的那些),\n * 本模块是**第二条独立证据链**:不吃那个推导,直接看盘。两条链互不依赖,\n * 才抓得住「推导本身错了」这一档 —— 只有一条时,推导错了与产物没问题同形。\n *\n * ⚠️ 判据是文件在不在,**不是**「这张码表爬成功了没有」:爬失败的表照样落一份\n * `completeness=unavailable` 的文件,指向它**有效**。\n *\n * 🔴 **射程只到 `buildMapping` 刚产出的 sheets**:本函数直接取 `sheet.fields` /\n * `field.key`,对**手拼的** sheet 不设防 —— 与 `mapping.ts` 给手拼调用方加的三道闸\n * (`mutationSection` / `allowedIn` / `unsafeKeyReason`)口径**故意不一致**。\n * 理由是射程不同:那三道闸守的是 `writeMapping` 这个真有手拼调用方的公开 API\n * (断点续跑读回、合并两轮结果),而这里只吃同一次调用里 `buildMapping` 刚产出的 sheets,\n * 形状由它保证。哪天它被搬去公开 API,形状闸要一起补上,不能只搬函数体。\n *\n * 🔴 **为什么单独一个模块**(Python 侧它是 `runner.py` 的模块私有函数):\n * `runner` 必须有一条守卫钉住「这次对账**真的被调用了**」——Python 用\n * `monkeypatch.setattr(runner_mod, ...)` 换掉模块属性,ESM 里没有这回事。\n * 拆成模块之后 `vi.mock` 换得掉,那条接线守卫才做得出来。\n * ⚠️ 拆分**不等于**公开:它不进 `packages/core/src/index.ts` 的导出面,\n * 上面那条射程纪律照旧成立。\n */\nimport { existsSync } from 'node:fs';\nimport * as path from 'node:path';\n\n/** 本函数吃的 sheet 形状——`buildMapping` 的产物是它的超集。 */\nexport interface AuditSheet {\n model: string;\n fields: Array<Record<string, unknown>>;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\n\n/** 扫每一份 sheet 里非空的 `ref.valueDomain`,逐个确认它指的文件**真的在磁盘上**。 */\nexport function auditValueDomainPointers(\n layout: { dir: string },\n sheets: readonly AuditSheet[],\n): string[] {\n const problems: string[] = [];\n // 同一个指针会在几百份 sheet 里重复出现(`createById → User` 那种量级),只看一次盘。\n const seen = new Map<string, boolean>();\n for (const sheet of sheets) {\n for (const field of sheet.fields) {\n const ref = field.ref;\n const pointer = isRecord(ref) ? ref.valueDomain : null;\n // 空串与 null 一样跳过:`buildMapping` 的取值域是「路径 | null」,\n // 空串两边都不是,报它等于对一个产不出来的形态发警报。\n if (typeof pointer !== 'string' || pointer === '') continue;\n if (!seen.has(pointer)) {\n seen.set(pointer, existsSync(path.join(layout.dir, pointer)));\n }\n if (!seen.get(pointer)) {\n problems.push(\n `${sheet.model}.${String(field.key)}: ref.valueDomain 指向 ${pointer},` +\n '但这份对照表没有落盘(悬空指针:顾问会以为它只是还没生成)',\n );\n }\n }\n }\n return problems;\n}\n","/**\n * 引用边集:整个侦察层里「哪个字段指向哪个模型」的**唯一**产生点(契约清单 §3.3,17 条)。\n *\n * 🔴 载体有两个,不是一个(2026-08-16 实测):\n * · `object`:2926 个字段,主载体(2924 个在 `type=uuid` 上,2 个在 `type=string` 上);\n * · `refer` :3 个字段,与 `object` **从不共现**。\n * 只读 `object` 会少掉 3 条边——这个量级在总数上看不出异常,\n * 正是「枚举载体,不是审改动」那条纪律要防的形态。加载体请改 `REF_CARRIERS`。\n *\n * 🔴 **不得**用 `isTree` / `multiple` / `treeLoadMode` 做任何判断:实测这三个键在全部\n * 14299 个字段上分别恒为 `false` / `false` / `\"lazy\"`,是序列化缺省而非信号。\n * 拿它们分支,另一侧夹具根本走不到,守卫看着有、实际是空的。\n *\n * topology 与 mapping 两份产物都从这里取边。收口在这一处,是为了让它们永远吃同一套判读——\n * 判读一旦分叉,差额(比如一边读了 `refer` 一边没读)只表现为两个数字不等,\n * 从产物里看不出是什么造成的。\n */\nimport { metaFields } from './fields';\n\n/**\n * 🔴 **顺序即优先级**:同一字段同时带两个载体时取靠前的,并且**只产出一条边**——\n * 产出两条会让 topology 的边数与字段数对不上,而这个差额从产物里看不出是共现造成的。\n * 实测中共现从未发生,但这是**代码不变量**,由两条用例钉住。\n */\nexport const REF_CARRIERS = ['object', 'refer'] as const;\nexport type RefCarrier = (typeof REF_CARRIERS)[number];\n\nexport interface RefEdge {\n fromModel: string;\n fromField: string;\n fieldName: string;\n toModel: string;\n carrier: RefCarrier;\n labelField: string | null;\n labelFieldSource: 'declared' | 'unknown';\n}\n\nexport interface DanglingRef {\n fromModel: string;\n fromField: string;\n /** `malformed-target` 那一档这里是**合成的字符串不是模型名**,按 `reason` 分流。 */\n toModel: string;\n carrier: RefCarrier;\n reason: 'not-in-registry' | 'malformed-target';\n}\n\nexport interface RefResult {\n edges: RefEdge[];\n dangling: DanglingRef[];\n}\n\n/**\n * → `{edges, dangling}`。\n *\n * · `edges`:目标在名册里的引用边,按 `(fromModel, fromField)` 排序;\n * · `dangling`:**声明了引用但用不了**的字段,两档靠 `reason` 区分:\n * - `not-in-registry`:目标不在名册里(实测 4 个)。它们不是「没爬到」,\n * 是产品自己声明了一个不存在的目标——顾问照着填会拿到 400。\n * 放进 edges 会让 topology 凭空多出几个不存在的节点,所以分开交出来。\n * - `malformed-target`:载体存在但值不是字符串。🔴 **不能并进「压根没声明引用」里静默吞掉**:\n * 那样「产品声明了一个坏形态的引用」与「这就是个普通字段」在产物里同形,\n * 顾问会把它当普通字段填,运行期才炸。\n *\n * 🔴 **排序是产物可 diff 的前提**:`metas` 的插入序**不是**恒定的\n * (定向重爬与全量爬出来的顺序不同)。不排序,两次跑同一个租户就会 diff 出满屏噪声,\n * 真正的元数据变更淹在里面。\n */\nexport function refEdges(metas: Record<string, unknown>, knownModels: Iterable<string>): RefResult {\n const known = new Set(knownModels);\n const edges: RefEdge[] = [];\n const dangling: DanglingRef[] = [];\n for (const modelKey of Object.keys(metas).sort()) {\n // 判读走 `metaFields` 单一口径——与 `extractFieldRows` 同源。\n // 这里**不再记一份 problem 台账**:同一份 metas 的坏载荷已由 `extractFieldRows`\n // 记进 dropped。这里再记一份,同一个坏载荷就有了两个来源,接线时极易被加两次。\n const { fields } = metaFields(metas[modelKey]);\n for (const field of fields) {\n if (!field || typeof field !== 'object' || Array.isArray(field)) continue;\n const rec = field as Record<string, unknown>;\n const fieldKey = rec.key;\n // 🔴 key 纪律必须与 `extractFieldRows` 里「没有可用的 key」那一支**逐字一致**——\n // 非字符串的 key 也要丢。只判真假会让 `key: 7` 这种字段活着进 edges,于是:\n // ① 同一个字段在 `index/fields.jsonl` 里不存在、在引用边里却存在,两份产物对不上账;\n // ② 排序时字符串与数字比大小当场抛——客户现场一条无源码可读的栈。\n // 复用了 `metaFields` 却不复用 key 纪律,等于只收口了一半。\n if (typeof fieldKey !== 'string' || !fieldKey) continue;\n // 按 `REF_CARRIERS` 循环,一处声明一处读。\n // 🔴 写成循环,是让「元组增了而实现没跟上」这个失败态**根本无从发生**的手段;\n // 守卫兜的是万一有人改回硬编码之后、下一个加载体的人。\n const carrier = REF_CARRIERS.find((c) => rec[c]);\n if (carrier === undefined) continue;\n const target = rec[carrier];\n // 🔴 只判类型,不判空——空值在上面那句 `find` 里就因假值没被选中,\n // 这里再判空是不可达死码(读的人会以为空串能走到这条分支)。\n //\n // 🔴 **这道门只关了一半,别以为关全了**:`find` 的分界线是**真假值**,不是「键在不在」。\n // 于是坏形态分成两拨:非空且非字符串(`{a:1}` / `[\"T\"]` / `5`)走到这里记 dangling ✅;\n // **假值的坏形态(`{}` / `[]` / `0` / `false`)压根没被选中,仍然无痕消失** ❌。\n // 为什么不顺手补上——已实测不是猜:现网 `object`/`refer` 的「键在但值为假」命中数是 **0**\n // (该序列化器对这几个键是**选择性发键**的),切成「键存在」判据在本租户等价。\n // 🔴 那为什么还是不切?**理由不是这个缺口不存在**,而是那张表只是一次观测、不是契约:\n // 别的环境若对 `object` 改成键恒发,切判据会让**每一个字段**都变成 malformed-target,\n // 爆炸半径是整份产物,而我们从 HTTP 面**证不了它不会**。\n // ⇒ 宁可留一个命中数为 0 的缺口,也不换一个爆炸半径是整份产物的判据。\n if (typeof target !== 'string') {\n dangling.push({\n fromModel: modelKey,\n fromField: fieldKey,\n toModel: reprOf(target),\n carrier,\n reason: 'malformed-target',\n });\n continue;\n }\n if (!known.has(target)) {\n dangling.push({ fromModel: modelKey, fromField: fieldKey, toModel: target, carrier, reason: 'not-in-registry' });\n continue;\n }\n const label = rec.objectLabel;\n const declared = typeof label === 'string' && label.length > 0;\n edges.push({\n fromModel: modelKey,\n fromField: fieldKey,\n // 🔴 字段中文名,是「grep 部门 → 落到 {model, key}」这条链上唯一的人话。\n // 接错成 key 会让产物里全是英文键名——语法完好、不报错,\n // 所以键集与取值都由「边的键集逐键钉死」那条用例守着。\n fieldName: typeof rec.name === 'string' ? rec.name : '',\n toModel: target,\n carrier,\n // 🔴 没声明就是 null + \"unknown\",**不许兜底成 \"name\"**:\n // 「不知道」必须与「猜出来的名字」可分。\n // 实测:2926 个 object 字段里只有 1557 个带 objectLabel;\n // 剩下的指向的目标里 60 个 meta 拿得到但**没有 name 字段**。\n // 落空时**不报错**——产物里会是一个语法完好、指向不存在列的显示字段名。\n labelField: declared ? (label as string) : null,\n labelFieldSource: declared ? 'declared' : 'unknown',\n });\n }\n }\n const byFrom = (a: { fromModel: string; fromField: string }, b: { fromModel: string; fromField: string }): number =>\n a.fromModel.localeCompare(b.fromModel) || a.fromField.localeCompare(b.fromField);\n edges.sort(byFrom);\n dangling.sort(byFrom);\n return { edges, dangling };\n}\n\n/** 坏目标的可追查表示。保留原值的形状线索,但它**不是模型名**。 */\nfunction reprOf(v: unknown): string {\n if (typeof v === 'string') return JSON.stringify(v);\n if (v === undefined) return 'undefined';\n try {\n return JSON.stringify(v) ?? String(v);\n } catch {\n return String(v);\n }\n}\n","/**\n * 码表与枚举的值域对照(契约清单 §3.8,44 条)。\n *\n * **存在理由(实测锚定)**:引用字段存的是目标行的 `id`(UUID),不是 `code` ——\n * `Employee.maritalStatusId = 019f6893-…` 对应 `CO_0001` 里 `code=20 / name=已婚` 那一行。\n * 客户 Excel 里写的是「已婚」,没有这份对照表就写不进去。\n *\n * 🔴 completeness 是四档不是三档,而且**「取满了」不等于「全集」**:查询引擎先注入 RBAC\n * 过滤再分页与 count,`totalElements` 本身就是**裁剪后**的可见总数。所以取满只能到\n * `visible-complete`;`authoritative-complete` 需要产品侧的权威值域出口 —— 那个出口\n * 现在不存在,本模块**没有任何分支**会产出它(有一条守卫扫 `collectCodeTables` 的源码\n * 钉住这一点,所以那个函数体里连注释都不许出现这个字符串)。\n *\n * 🔴 规模是量过的,不是估的:72 张 DYNAMIC 表合计 9417 行,最大一张 3212 行,\n * 空表 0 个,`size:0` 探针 72/72 有应答。⇒ 全量快照可行,**不设抽样**。\n *\n * 🔴 **上限只是「每表」的护栏,不是抽样**:本租户一次都不触发,它挡的是\n * 「换个客户,某张码表几十万行」那一档 —— 一张表就能吃掉整轮。\n * 被截断的表按「no silent caps」**两处留痕**:表自己的产物文件\n * (`completeness=partial` + `truncatedBy`)与 `problems`,缺一处等于没记。\n */\nimport type { Outcome, ReconProbe } from '../probe';\nimport type { ReconLayout } from '../layout';\nimport { LayoutContainmentError } from '../layout';\nimport { metaFields } from './fields';\n// 🔴 与编排层 / mapping 同一个键,不抄字面量。\nimport { KIT_CARRY_OVER_KEY } from './carry-over';\n\nexport const COMPLETENESS = ['authoritative-complete', 'visible-complete', 'partial', 'unavailable'] as const;\n/**\n * 实测 `size:5000` 未被截断,本仓未观测到 size 上限。500 是为了让单次响应体\n * 不至于大到在客户 VPN 上超时,不是协议约束。\n */\nexport const PAGE_SIZE = 500;\n/**\n * 🔴 判码表用 `type`,不用 `CO_` 前缀:实测两个集合双向差集为空,但前缀是命名巧合,\n * type 才是产品的声明。前缀一旦被某个租户用在别处,前缀判法会静默多爬/少爬。\n */\nexport const DYNAMIC_TYPE = 'DYNAMIC';\n\n/**\n * 每表上限(顾问可改)。\n *\n * 🔴 **两个默认值都是按实测定的,每个数都能回源**(本机 / 租户 13 实跑,走生产同一条码路):\n * · 最大的一张码表 3212 行 ⇒ 计数探针 1 次 + 7 页 = **8 次请求 / 1.652s**;\n * · 72 张表合计 **156 次请求 / 6.65s**;整轮 recon 全量墙钟 15.16s。\n * ⇒ 请求上限取 **40**(实测最大值的 5 倍,≈ 20000 行/表),墙钟上限取 **30s**\n * (实测最大值的 18 倍)。两个都留了一个数量级的余量,本租户**一次都不触发**。\n *\n * ⚠️ **客户 VPN 的 RTT 至今没有实测**。本机单次往返中位 3.2ms;若按「假设的 200ms」外推,\n * 8 次请求 ≈ 1.6s 的往返,仍远在 30s 之内 —— 但这一句是**外推不是实测**,\n * 别把它转述成量过的。真到了 RTT 高的现场,先撞上的会是请求上限而不是墙钟。\n *\n * 🔴 上限是**每表**的,超限只停这一张、继续下一张 —— 不是整轮失败。\n */\nexport const MAX_REQUESTS_PER_TABLE = 40;\nexport const MAX_SECONDS_PER_TABLE = 30.0;\n\nexport const WORDING =\n 'rows = 本轮以当前登录身份读到的码表行;每行原样保留服务端返回的全部键。' +\n '引用字段存的是这里的 `id`,不是 `code`。' +\n 'completeness: visible-complete = **当前角色可见范围内**取满(不是全集,RBAC 裁剪先于 count);' +\n 'partial = 没取满或有页失败**或触发了工具的每表上限**(后者看这张表的 `truncatedBy`:' +\n 'null = 没触发,对象 = 停在第几页 / 已收多少行 / 触发的是哪个上限 —— 那是**工具**的截断,' +\n '不是服务端的毛病,改上限重跑就能取全);' +\n 'unavailable = 连计数都没读到(crawled=false,与「这张码表是空的」是两回事);' +\n 'authoritative-complete = 需要产品侧权威值域出口,**本版本永远不会产出**。' +\n 'partial / unavailable 时禁止据此生成写脚本。';\n\nfunction listPath(modelKey: string): string {\n return `/api/models/${modelKey}/list/dsl`;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\n\ninterface ContentRead {\n rows: Array<Record<string, unknown>> | null;\n total: number | null;\n nonDict: number;\n}\n\n/**\n * 判读一份 list/dsl 响应;形状不对时 `rows`/`total` 都是 null。\n *\n * 实测的封套是**顶层无 `data` 包装**:\n * `content, page, size, totalElements, totalPages, first, last, numberOfElements, empty`。\n *\n * 🔴 分开返回而不是抛:一张码表的响应畸形不该让另外 71 张跟着丢。\n *\n * 🔴 `nonDict` 不是可有可无的第三个返回值 —— `content` 里混进非对象元素时,\n * 只过滤不上报会让差额只表现为「计数 3、取回 2、partial」,顾问会照着去查 RBAC,\n * 而真相是响应里有个字符串。丢什么必须有出处。\n *\n * ⚠️ **布尔那一档在 TS 里与 Python 不同源,但判据照留**:Python 里 `isinstance(True, int)`\n * 为真,`totalElements: true` 会被当成「共 1 行」;JS 的 `typeof true === 'boolean'`,\n * `Number.isInteger(true)` 本来就是 false。⇒ 这里**不是**在补那个 Python 陷阱,\n * 而是把「必须是非负整数」这条正面判据写全 —— 布尔、浮点、字符串、负数一并拒。\n */\nfunction readContent(body: unknown): ContentRead {\n if (!isRecord(body)) return { rows: null, total: null, nonDict: 0 };\n const rows = body.content;\n const total = body.totalElements;\n if (!Array.isArray(rows)) return { rows: null, total: null, nonDict: 0 };\n if (typeof total !== 'number' || !Number.isInteger(total) || total < 0) {\n return { rows: null, total: null, nonDict: 0 };\n }\n const kept = rows.filter((r): r is Record<string, unknown> => isRecord(r));\n return { rows: kept, total, nonDict: rows.length - kept.length };\n}\n\nexport interface TruncatedBy {\n limit: 'requests' | 'wallclock';\n limitValue: number;\n requestsSent: number;\n elapsedSeconds: number;\n stoppedAtPage: number;\n rowsRetrieved: number;\n totalElements: number;\n}\n\nexport interface CodeTable {\n model: string;\n crawled: boolean;\n completeness: (typeof COMPLETENESS)[number];\n totalElements: number | null;\n rowsRetrieved: number;\n rows: Array<Record<string, unknown>>;\n probeStatus: number;\n probeError: string;\n truncatedBy: TruncatedBy | null;\n wording: string;\n}\n\nexport interface CollectCodeTablesOptions {\n maxRequests?: number;\n maxSeconds?: number;\n /**\n * 🔴 **可注入的单调时钟**(默认 `performance.now()` 的秒数),只为让墙钟上限那条守卫\n * 不必靠 sleep 去撞。用 sleep 造的用例,其判据依赖「调度器按时唤醒我」这个没人保证的\n * 前提 —— 负载一高就随机红,然后被放宽阈值,正是「判据必须能自证前提成立」记的那条病。\n * 注入后判据变成「墙钟走到 X 就必须停」。\n * ⚠️ 用单调时钟不用墙上时间:后者会被校时往回拨,一次回拨就能让「已经花了多久」\n * 算出负数,上限于是永不触发。\n */\n clock?: () => number;\n}\n\n/**\n * → `{tables, problems}`。`tables` 以 modelKey 为键,每值是一份可直接落盘的快照。\n *\n * 🔴 **问题文案只带 HTTP status 与响应分类,不解析服务端错误体**(显式取舍)。\n * 实测后端有**两套**错误信封,顶层都有 `code` 但语义相反:鉴权/框架层的 `code` 是数字\n * 状态码,业务层的 `code` 是字符串枚举、人话在 `details` 里。只认一套,另一套就会被读成\n * 「服务端说 code=400」这类**语法完好、指向错误**的文案 —— 比不带理由更坏。\n * 而响应分类(unauthorized / forbidden / network / server-error / bad-response)\n * 已经是决定顾问下一步动作的那一维,够用。\n */\nexport async function collectCodeTables(\n probe: ReconProbe,\n modelEntries: unknown[],\n opts: CollectCodeTablesOptions = {},\n): Promise<{ tables: Record<string, CodeTable>; problems: string[] }> {\n const maxRequests = opts.maxRequests ?? MAX_REQUESTS_PER_TABLE;\n const maxSeconds = opts.maxSeconds ?? MAX_SECONDS_PER_TABLE;\n const tick = opts.clock ?? (() => performance.now() / 1000);\n const post = probe.post;\n if (!post) throw new Error('探针没有 post 能力,取不了 list/dsl —— 这是编排的配置错,不是数据问题');\n\n const tables: Record<string, CodeTable> = {};\n const problems: string[] = [];\n const keys: string[] = [];\n for (const entry of modelEntries) {\n if (!isRecord(entry) || entry.type !== DYNAMIC_TYPE) continue;\n const key = entry.modelKey;\n // 🔴 key 纪律与 `extractFieldRows` / `refEdges` 逐字一致:**非字符串也要丢**。\n // 只判真假会让 `modelKey: 7` 活着进来,于是 ① 下面排序时当场类型错;\n // ② 不崩的时候更坏 —— 它会被拼进 URL 路径,产出一个谁也对不上的产物文件名。\n if (typeof key !== 'string' || !key) {\n // 静默跳过会让「名册里有个没名字的 DYNAMIC 条目」与「名册里压根没有它」同形。\n problems.push(`名册里有一条 type=DYNAMIC 的条目但 modelKey 不可用(${JSON.stringify(key)}),已跳过`);\n continue;\n }\n keys.push(key);\n }\n\n for (const key of [...new Set(keys)].sort()) {\n const path = listPath(key);\n // 🔴 每表预算从**这张表的第一发**开始算,计数探针也算一次请求:\n // 上限的语义是「这一张表最多花多少」,把探针排除在外会让「上限 1」这种极端配置\n // 变成「探针 + 1 页」,与常量名说的不是一回事。\n const started = tick();\n let requests = 0;\n let truncatedBy: TruncatedBy | null = null;\n // ① 先探计数。🔴 计数与取数必须在**同一轮里紧邻**发出:中间隔了别的工作\n // (比如把 72 张表的计数一次批量探完再回头取页),「取满了没有」这个判据的前提\n // (这中间总数没变)就不成立了 —— 判据会在别人写入的时候安静地说谎。\n const probeOut: Outcome = await post.call(probe, path, { page: 0, size: 0 });\n requests += 1;\n let total: number | null = null;\n let reason: string | null = null;\n if (probeOut.error !== 'ok') {\n reason = `计数探针失败(HTTP ${probeOut.status} / ${probeOut.error})`;\n } else {\n total = readContent(probeOut.body).total;\n // 🔴 「没答」与「答了但形状用不了」都落 unavailable(都拿不到计数),但文案必须分开:\n // 合并成一句「计数探针失败」会让顾问对着一个 HTTP 200 去查 VPN 和权限。\n reason = total !== null ? null\n : `计数探针答了 HTTP ${probeOut.status} 但响应形状不对(content 不是数组 / totalElements 不是非负整数)`;\n }\n if (total === null) {\n problems.push(`${key}: ${reason},本码表标 unavailable`);\n tables[key] = {\n model: key, crawled: false, completeness: 'unavailable',\n totalElements: null, rowsRetrieved: 0, rows: [],\n probeStatus: probeOut.status, probeError: probeOut.error,\n // 🔴 恒发键:这一档是「连计数都没读到」,与「取数时撞了上限」是两回事,\n // 缺键会被读成「这个工具还不懂上限」。\n truncatedBy: null, wording: WORDING,\n };\n continue;\n }\n\n // ② 逐页取。🔴 用 totalElements 定页数,而不是「取到空页就停」——\n // 中途一页返空(网关截断)会被当成正常结束,静默少读一半。\n const rows: Array<Record<string, unknown>> = [];\n let page = 0;\n let failed: string | null = null;\n // 🔴 熔断用:已见过的行身份。它必须在**循环内**发挥作用 —— 循环结束后才算的重复检测\n // 在「服务端忽略 page + totalElements 是脏数据 9,000,000」这种组合下**永远轮不到执行**:\n // 每页 2 行、循环 450 万次请求,现场表现是「工具卡死、无任何输出」。\n const seenIds = new Set<string>();\n let prevPage: string | null = null;\n while (rows.length < total) {\n // 🔴 上限判在**发请求之前**:语义是「不许再多发一发」,而不是「发完再看超没超」。\n // 后者每次都会比上限多一发,且「上限 = N」在不同表上意味着 N 或 N+1 次请求,\n // 顾问按常量算预算就永远对不上。\n // ⚠️ 它与下面的分页判据是**另一个维度**:那边判「服务端行为不对」,这里判\n // 「工具自己的预算用完了」。两者的现场处置不同(前者查服务端,后者调上限),\n // 所以谁先命中就由谁收尾,而 `truncatedBy` 只在**本条**命中时才非空。\n const elapsed = tick() - started;\n if (requests >= maxRequests || elapsed >= maxSeconds) {\n const which = requests >= maxRequests ? 'requests' : 'wallclock';\n truncatedBy = {\n limit: which,\n limitValue: which === 'requests' ? maxRequests : maxSeconds,\n requestsSent: requests,\n elapsedSeconds: Math.round(elapsed * 1000) / 1000,\n stoppedAtPage: page,\n rowsRetrieved: rows.length,\n totalElements: total,\n };\n failed =\n `触发工具的每表${which === 'requests' ? '请求' : '墙钟'}上限(` +\n `${which === 'requests' ? `已发 ${requests} 次请求` : `已花 ${elapsed.toFixed(3)}s`}),` +\n `停在第 ${page} 页,已收 ${rows.length} 行 / 计数 ${total} 行,` +\n '本表标 partial。**这是工具的截断,不是服务端的毛病** —— ' +\n `调大 valuedomain.${which === 'requests' ? 'MAX_REQUESTS_PER_TABLE' : 'MAX_SECONDS_PER_TABLE'} ` +\n `或给 collectCodeTables 传 ${which === 'requests' ? 'maxRequests' : 'maxSeconds'} 后重跑就能取全`;\n break;\n }\n const out: Outcome = await post.call(probe, path, { page, size: PAGE_SIZE });\n requests += 1;\n if (out.error !== 'ok') {\n failed = `第 ${page} 页失败(HTTP ${out.status} / ${out.error})`;\n break;\n }\n const { rows: got, total: pageTotal, nonDict } = readContent(out.body);\n if (got === null) {\n failed = `第 ${page} 页响应形状不对`;\n break;\n }\n // 🔴 每页响应自带的 `totalElements` 是**这个前提唯一的自证材料**,不许丢。\n // 上面那句「计数与取数紧邻发出」只是把窗口**缩小**,并没有**校验**「这一轮里总数没变」\n // —— 而码表恰恰是实施期顾问一边导码值、一边跑 recon 的对象。\n // 实测形态:探针探到 1000,取数期间有人插了 500 行,两页各 500 行且 id 全不重复\n // ⇒ id 重复守卫抓不到、`rows.length === total` 成立 ⇒ 产物宣布 visible-complete、\n // problems 全空,而实际只有 2/3 的码值 —— 而 visible-complete 正是「放行生成写脚本」那一档。\n //\n // 🔴 观测与处置分开:先把「本页有没有推进」**算出来**,再决定谁来收尾。\n // 放在计数变化之后判的话,两个症状同时存在时熔断那句**永远不会出现** ——\n // 顾问被指向「有人在写这张码表」,而现场同时还有「服务端忽略了 page」这条更硬的病。\n //\n // 🔴 熔断:本页**一行新的都没带来** ⇒ 再翻多少页也不会有进展,立刻停。两条判据互补:\n // ① 全部可判身份的行:本页 id 全是见过的 ⇒ 服务端在重复发同一页;\n // ② 一行都没有 id(① 判不了):本页内容与上一页**逐字相同** ⇒ 同上。\n // 🔴 ① 要求本页**每一行都可判身份**才敢下结论。少了这个前提就会假阳性:\n // 一页里只有 1 行带 id 且是旧的、其余 499 行是**全新的无 id 行** ⇒ 子集成立 ⇒ 熔断,\n // 止步 500 行,还报「服务端可能忽略了 page」—— 而服务端**根本没有**忽略。\n // ⚠️ 由此留下的边界:混合页且「有 id 的行全旧、无 id 的行也没推进」时两条都不触发,\n // 此时仍由 `total` 兜底。熔断的**触发**是可靠的,但它**不触发**不等于分页正常。\n //\n // ⚠️ ② 在 TS 里必须显式深比较:JS 的 `===` 对两个内容相同的数组是 false,\n // 直接照抄 Python 的 `got == prev_page` 会让这一支**永不触发**。用 JSON 序列化比。\n const pageIds = new Set(got.filter((r) => typeof r.id === 'string').map((r) => r.id as string));\n const serialized = JSON.stringify(got);\n const allIdentifiable = pageIds.size > 0 && pageIds.size === got.length;\n const noProgress =\n got.length > 0 &&\n ((allIdentifiable && [...pageIds].every((i) => seenIds.has(i))) ||\n (pageIds.size === 0 && prevPage !== null && serialized === prevPage));\n // ⚠️ 第 0 页上 `noProgress` 恒为 false,这不是漏判:那时**还没有证据**能说分页推没推进。\n // 所以计数变化在第 0 页触发时只报计数变化,不许顺口猜「分页也坏了」。\n if (pageTotal !== total) {\n failed = `取数期间计数从 ${total} 变成 ${pageTotal}(有人在写这张码表?本轮快照不完整)`;\n // 两个症状都留痕:它们指向的现场原因不同,只报一个会把顾问引偏。\n if (noProgress) failed += ';同一页还观察到分页没有推进(服务端可能也忽略了 page 参数)';\n break;\n }\n if (nonDict) {\n // 不 break:这一页里正常的行照收,但差额的出处要留下。\n problems.push(`${key}: 第 ${page} 页有 ${nonDict} 个非对象元素,未计入 rows`);\n }\n if (got.length === 0) {\n failed = `第 ${page} 页返空但还差 ${total - rows.length} 行`;\n break;\n }\n if (noProgress) {\n failed =\n `第 ${page} 页没有带来任何新行(服务端可能忽略了 page 参数),` +\n `已熔断,止步 ${rows.length} 行 / 计数 ${total} 行`;\n break;\n }\n for (const i of pageIds) seenIds.add(i);\n prevPage = serialized;\n rows.push(...got);\n page += 1;\n }\n\n // 🔴 前提要显式断言。`rows.length === total ⇒ 取满了` 这个判据依赖一个没人保证的前提:\n // **各页不重叠**。服务端忽略 `page` 参数(或反代把分页参数吃掉)时,同一页会被取回 N 遍,\n // 行数照样凑够 total,于是产物宣布 visible-complete,而里面是 N 份重复、\n // 缺了 (N-1)/N 的真实取值 —— 顾问拿它做对照表,缺的那些码值一个都对不上,\n // 且现场没有任何迹象指向分页。这条在写本模块时**实际发生过**。\n // ⚠️ 只能校验带字符串 `id` 的行 —— 没有 `id` 就没有身份,这里不替它编一个,\n // 也不因此声称「查过了」。\n // ⚠️ 这一条与循环里的熔断**不是重复**:熔断抓「整页都没有新行」(否则连不到这里就已经\n // 卡死几百万次请求),本条抓「每页都有一些新行、但页与页之间部分重叠」——\n // 那种情况循环能正常结束,只有事后按身份去重才看得出来。\n const collectedIds = rows.filter((r) => typeof r.id === 'string').map((r) => r.id as string);\n const duplicated = collectedIds.length - new Set(collectedIds).size;\n const complete = failed === null && rows.length === total && !duplicated ? 'visible-complete' : 'partial';\n if (duplicated) {\n problems.push(\n `${key}: 取回的 ${rows.length} 行里有 ${duplicated} 行 id 重复,分页很可能没生效` +\n '(服务端忽略了 page?),行数凑够 totalElements 也不算取满',\n );\n }\n if (failed) {\n problems.push(`${key}: ${failed}`);\n } else if (rows.length !== total) {\n // 取回数 < 计数而每页都成功:RBAC 裁剪先于分页与 count 时的形态。\n problems.push(`${key}: 取回 ${rows.length} 行但计数是 ${total} 行`);\n }\n tables[key] = {\n model: key, crawled: true, completeness: complete,\n totalElements: total, rowsRetrieved: rows.length, rows,\n probeStatus: probeOut.status, probeError: probeOut.error,\n // 🔴 两处落点之一(另一处是上面的 `problems`):**产物是被单独拷走的那一半**,\n // 只进 problems 的话,顾问 `cat valuedomain/CO_X.json` 看到 partial 却查不出是被谁截的。\n // `null` = 查过了没触发,对象 = 触发了(恒发键)。\n truncatedBy,\n wording: WORDING,\n };\n }\n return { tables, problems };\n}\n\nexport interface EnumDomainRow {\n model: string;\n field: string;\n fieldName: string;\n count: number;\n enums: Array<Record<string, unknown>>;\n nonDictElements: string[];\n}\n\n/**\n * 枚举字段的值域。\n *\n * 🔴 **元素原样落盘(verbatim),不挑键名**。实测 3179 个枚举元素有三种形状:\n * `{key,label,value}` × 2473、`+type` × 549、`+type,effect` × 157,三个键零空值。\n * 上一刀只留了 `value`+`label`,把 `key` 丢了;现在挑键名还会再丢 `type`/`effect`。\n * 「拿一个生产样本论证元素形态」是运气不是免疫 —— 换个元素形状就复发一次。\n *\n * ⚠️ 这里与 `fields.enumRows` **故意不同源**,两份产物的职责不同:\n * `index/fields.jsonl` 是**归一后**的可 grep 视图(缺 value 会退化补齐并打\n * `_kitValueSource` 认领标记);本产物是**逐字镜像**,不加工也不认领。\n * 要改的话请一起想清楚:把镜像也归一了,就再没有地方能看到产品原样发了什么。\n */\nexport function collectEnumDomains(metas: Record<string, unknown>): EnumDomainRow[] {\n const rows: EnumDomainRow[] = [];\n // 🔴 排序是产物可 diff 的前提:`metas` 的键序不恒定(定向重爬与全量爬的顺序不同),\n // 不排序两次跑同一个租户就 diff 出满屏噪声。\n for (const modelKey of Object.keys(metas).sort()) {\n // 判读走 `metaFields` 单一口径。这里不再记一份 problem 台账:\n // 同一份 metas 的坏载荷已由 `extractFieldRows` 记进 `dropped`,重复记会被加两次。\n const { fields } = metaFields(metas[modelKey]);\n for (const field of fields) {\n if (!isRecord(field)) continue;\n const fieldKey = field.key;\n // 🔴 与 `extractFieldRows` 的 key 判据逐字一致:**非字符串的 key 也要丢**。\n // 只判真假的话,`key: 7` 这个字段在 index/fields.jsonl 里被丢掉、在 `_enums.jsonl`\n // 里却存在 —— 同一份 metas 的两个消费者各判各的,两份产物对不上账,且谁也不报错。\n if (typeof fieldKey !== 'string' || !fieldKey) continue;\n const enums = field.enums;\n if (!Array.isArray(enums) || enums.length === 0) continue;\n // 浅拷贝:同一份 metas 还要喂给 fields / refs / mapping,直接挂原对象会让下游改产物时\n // 串改到 metas,而串扰在产物里完全看不出来。\n const kept = enums.filter(isRecord).map((e) => ({ ...e }));\n rows.push({\n model: modelKey,\n field: fieldKey,\n fieldName: (typeof field.name === 'string' && field.name) || '',\n count: kept.length,\n enums: kept,\n // 🔴 非对象元素进不了 `enums`(那一列的契约是「每项都是对象」),但也不许无痕消失\n // —— 否则「这个字段的枚举少了一项」这件事没有任何出处。\n // 实测 3179 条全是对象,这一档从未观测到;**恒发空列表**而不是条件出键:\n // 「有这一列且为空」与「没这一列」是两个值,后者读起来像「没检查过」。\n nonDictElements: enums.filter((e) => !isRecord(e)).map((e) => JSON.stringify(e) ?? String(e)),\n });\n }\n }\n return rows;\n}\n\n/**\n * 🔴 `valuedomain/` 这一层**保留的文件名**:索引与枚举镜像。\n * ⚠️ **落盘那两处用的仍是字面量**,不是从这里取 —— 说清楚免得下一个人以为改这里就够了。\n * ⇒ 这里是**判据与文案**的单一来源;将来再加工具自己的产物文件,两处都要动。\n */\nexport const RESERVED_FILENAMES = ['_index.json', '_enums.jsonl'] as const;\n\nconst PATH_SEPARATORS = ['/', String.fromCharCode(92)];\n\n/**\n * 码表 modelKey 能不能安全地当 `valuedomain/<key>.json` 用 → 不能则给出人话理由。\n *\n * 🔴 **抽成模块级函数是承重的,不是整理代码**:`writeValuedomain` 用它决定「写不写」,\n * 编排层用它决定「这张表的文件到底是不是它自己的快照」。两处必须问**同一个问题** ——\n * 各判各的正是一条真缺陷的成因:编排层原先只按「路径在不在 `written` 里」推导,\n * 而 `written` 里**恒有** `valuedomain/_index.json`(索引自己写的那一份),\n * 于是一张叫 `_index` 的码表被判成「文件写出来了」,mapping 照着发指针,\n * 两条对账链**都报干净**,而指针解析出来的是索引。\n *\n * 🔴 本地判据与落盘闸是**纵深防御的两层,职责不同**,别合并也别删:\n * 落盘闸抛异常(fail-loud,但调用方得接住、且理由是路径口径的);\n * 本地判据能把「哪张码表、为什么没落盘」**留痕进 `writeProblems`**,\n * 顾问在产物里才看得见 —— 那是他唯一的现场信息。\n *\n * 本地判据的射程是这三种(`..` 越目录那一档由落盘闸的分量判据守):\n * ① 路径分隔符:把快照塞进子目录,`ls valuedomain/` 按 modelKey 找不回来;\n * ② 前导 `.`:产出 `ls` 看不见的隐藏文件;\n * ③ 前导 `_`:`_index` / `_enums` 是**本目录工具自己的保留文件名**,\n * 一张叫 `_index` 的码表会把这份索引顶掉 —— 而顶掉之后 `writeProblems` 是空的、\n * 该表还照常出现在 `byCompleteness` 里,像正常落了盘一样。\n */\nexport function unsafeTableKeyReason(key: string): string | null {\n if (PATH_SEPARATORS.some((s) => key.includes(s))) {\n return `modelKey 含路径分隔符,落盘会写出 valuedomain/ 子目录:${JSON.stringify(key)}`;\n }\n if (key.startsWith('.')) return `modelKey 以 . 开头,会产出 ls 看不见的隐藏文件:${JSON.stringify(key)}`;\n if (key.startsWith('_')) {\n // 不只判 `_index` / `_enums` 两个具体名字:整段前缀留给工具,下一个 `_xxx` 产物\n // 才不必回来再补一条(而漏补的表现是静默顶掉那份新产物)。\n return (\n `modelKey 以 _ 开头,与本目录工具自己的保留文件名(${RESERVED_FILENAMES.join('、')})` +\n `同一命名空间,会顶掉它们:${JSON.stringify(key)}`\n );\n }\n return null;\n}\n\nexport interface WriteValuedomainOptions {\n identity?: unknown;\n modelEntriesSeen?: number | null;\n carryOver?: unknown;\n problems?: string[] | null;\n roundId?: string | null;\n}\n\n/**\n * 落盘:`valuedomain/<modelKey>.json` 每表一份 + `_enums.jsonl` + `_index.json`。\n *\n * 一表一文件而不是一个大 JSON:顾问在现场是按「客户 Excel 里这一列对应哪张码表」逐张查的,\n * 一张 3212 行的表塞进合集里会让每次查看都要加载全部 9417 行。\n *\n * 🔴 **逐张接异常,一张表的名字有毒不许炸掉整批**。落盘闸对越界的 rel 会抛;原先没接,\n * 于是一个越界 modelKey 会中断**整个**落盘 —— 另外 71 张表的产物、`_enums.jsonl`、\n * 连同整份 `_index.json` 一起消失。爆炸半径不对。\n *\n * 🔴 **只接越界那一档,别的照旧往上抛**。我们要的是「不让它炸掉整批」,**不是**「把它吞掉」\n * —— 所以必须留痕进 `writeProblems`。接住一切会把磁盘满 / 只读挂载 / 权限一并吞成一条\n * 产物问题,而那类故障下**整批**都写不出去,报成「这一张有问题」是把系统性故障伪装成个例。\n *\n * 🔴 **写入顺序是承重的**:码表 → `_enums.jsonl` → `_index.json` **最后**。\n * 索引最后写才能把 `writeProblems` 装进去;否则一张表静默缺席,与「这张表压根不存在」\n * 在产物里同形 —— 顾问翻遍 `valuedomain/` 找不到某张码表,无从知道是没爬到、\n * 没落盘、还是产品里真没有。\n */\nexport function writeValuedomain(\n layout: ReconLayout,\n codeTables: Record<string, CodeTable>,\n enums: EnumDomainRow[],\n opts: WriteValuedomainOptions = {},\n): { written: string[]; writeProblems: string[] } {\n const written: string[] = [];\n const writeProblems: string[] = [];\n for (const key of Object.keys(codeTables).sort()) {\n const reason = unsafeTableKeyReason(key);\n if (reason !== null) {\n writeProblems.push(`${key}: ${reason},已拒绝`);\n continue;\n }\n try {\n written.push(layout.writeJson(`valuedomain/${key}.json`, codeTables[key]));\n } catch (err) {\n // 🔴 **只接越界那一档,别的照旧往上抛**(移植时这里写漏过一次,接住了一切)。\n //\n // ⚠️ **可达性说准(实测,不是推的)**:把量到的形态逐个打一遍 ——\n // 落盘闸判越界的只有 `../evil` / `../../evil` 两种,而它们**本地判据已经先拦掉了**;\n // 本地放行的(`foo..bar` / `CO_OK`)落盘闸一律放行。\n // ⇒ 下面那句 `writeProblems.push` 在当前实现下**走不到**。留着它不是为了覆盖率,\n // 是因为落盘闸在另一个模块、它的判据变过(曾经不拦 `..` 分量),\n // 两层的射程一旦再错开,这里就是唯一不让整批炸掉的地方。\n // 而 `throw` 那一半**是被覆盖的**:超长 modelKey 触发的 ENAMETOOLONG 实测抛的是\n // 普通 Error(不是越界),照这条原样往上抛 —— 那类是系统性故障,不该逐条吞掉。\n // 落盘闸的拒绝是**故意的安全闸**,我们要的是「不炸掉整批」而不是「吞掉」。\n // 接住一切会把磁盘满 / 只读挂载 / 权限一并吞成一条产物问题 ——\n // 而那类故障下**整批**都写不出去,报成「这一张有问题」是把系统性故障伪装成个例。\n if (!(err instanceof LayoutContainmentError)) throw err;\n // 原始异常消息一起带出来:里面有被拒的 rel 与产物根,是顾问唯一的现场信息。\n writeProblems.push(`${key}: 落盘被拒,本码表没有产物文件(${(err as Error).message})`);\n }\n }\n written.push(layout.writeJsonl('valuedomain/_enums.jsonl', enums));\n // 🔴 索引里带取数时的权限画像:同一份产物换个角色跑出来的可见范围不同,\n // 不记身份就没法判断两份产物为什么不一样。\n written.push(\n layout.writeJson('valuedomain/_index.json', {\n crawled: true,\n wording: WORDING,\n // 🔴 轮次身份。这份索引会被**单独拷走**,而一次中断会让它留在上一轮 ——\n // 那时 `valuedomain/` 里缺某张码表与「那张表真的没有」同形。\n // `null` = 调用方没告诉我(与 identity / modelEntriesSeen 同口径),恒发键。\n roundId: opts.roundId ?? null,\n // 🔴 `identity || {}` 会把「调用方没传身份」和「身份是空的」压成同一个值,\n // 而它们的下一步不同(前者去补工具的登录,后者去问服务端为什么不给 user)。\n identity: isRecord(opts.identity) ? opts.identity : null,\n codeTableCount: Object.keys(codeTables).length,\n enumFieldCount: enums.length,\n // 🔴 名册里一共看到几条。上游名册加载失败给出空数组时,产物会与\n // 「名册 742 条里一条 DYNAMIC 都没有」**逐字节同形**。\n // `null` = 调用方没告诉我(与 identity 同口径),`0` = 名册确实是空的。\n modelEntriesSeen: opts.modelEntriesSeen ?? null,\n // 🔴 定向重爬披露。这份产物**会被单独拷走**,而 `_enums.jsonl` 里非重爬模型的枚举\n // 来自**沿用的 meta** —— 不在这份索引里说一声,拿走 `valuedomain/` 的人就会把整册\n // 都当成本轮观测到的。**恒发键**:缺席不许兼职「本轮是全量」。\n [KIT_CARRY_OVER_KEY]: opts.carryOver ?? null,\n // 🔴 四档全列,产不出来的那一档给空数组而不是缺键:下游据这份索引决定放不放行写入,\n // 缺键会被读成「这个工具还不懂这一档」,空数组才是「懂,且本轮一个都没有」。\n byCompleteness: Object.fromEntries(\n COMPLETENESS.map((c) => [\n c,\n Object.entries(codeTables).filter(([, v]) => v.completeness === c).map(([k]) => k).sort(),\n ]),\n ),\n // 🔴 `byCompleteness` 里有名字、这里却列着的表 = 爬到了但没落盘。\n // 两者一起看才知道「找不到某张码表的文件」是哪一种原因。\n writeProblems,\n // 🔴 **取数期**的台账,与上面那份**落盘期**的不是一回事,合并会把「有这张表但内容残」\n // 和「压根没有这份文件」压成一句话。明细与摘要两处都要有,缺一份都等于没记。\n // `null` = 调用方没告诉我,`[]` = 对过账且一条都没有。\n problems: opts.problems ?? null,\n }),\n );\n return { written, writeProblems };\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["fs","path","fs","yaml","yaml","fs","yaml","ExitCode","CliErrorCode","yaml","fs","fs","path","yaml","yaml","path","action","yaml","yaml","fs","os","path","yaml","resolve","reject","randomUUID","randomUUID","resolve","fs","fs","path","manifest","buildMiniAppTemplateFiles","manifest","asRecord","action","path","yaml","JSON_SCHEMA","manifest","path","yaml","JSON_SCHEMA","path","createHash","readFileSync","path","createHash","isPlainObject","sha256","path","randomBytes","existsSync","readFileSync","path","modelKeys","path","modelKeys","path","action","WORDING","modelKeys","AUDIT_SET","WORDING","isRecord","typeName","modelKeys","path","existsSync","path","isRecord","WORDING","isRecord","modelEntries","path","PATH_SEPARATORS","randomBytes","isRecord","readFileSync","existsSync","state","enumRows"]}
|
|
1
|
+
{"version":3,"sources":["../src/http/trace.ts","../src/http/client.ts","../src/http/interceptors.ts","../src/package-version.ts","../src/auth/token-store.ts","../src/config/paths.ts","../src/auth/principal.ts","../src/auth/password.ts","../src/auth/refresh.ts","../src/config/profile.ts","../src/config/global-config.ts","../src/config/env.ts","../src/errors/codes.ts","../src/errors/cli-error.ts","../src/config/identity.ts","../src/config/migration.ts","../src/config/resolve.ts","../src/models/query.ts","../src/models/action.ts","../src/meta/describe.ts","../src/models/create.ts","../src/models/update.ts","../src/models/remove.ts","../src/import/fields.ts","../src/import/placeholder.ts","../src/import/parse.ts","../src/import/runner.ts","../src/client.ts","../src/documents.ts","../src/output/json.ts","../src/output/yaml.ts","../src/output/table.ts","../src/chat/display-width.ts","../src/output/index.ts","../src/auth/client-credentials.ts","../src/auth/pat.ts","../src/auth/pairing.ts","../src/auth/change-password.ts","../src/auth/admin-reset-password.ts","../src/chat/ws-client.ts","../src/chat/stream-iterator.ts","../src/chat/control-channel.ts","../src/chat/delegation-constants.ts","../src/chat/v4-reducer.ts","../src/chat/one-shot.ts","../src/chat/conversation-state.ts","../src/chat/conversation.ts","../src/chat/resume-summary.ts","../src/chat/conversation-list.ts","../src/mini/types.ts","../src/mini/workspace-files.ts","../src/mini/project.ts","../src/mini/templates.ts","../src/cache.ts","../src/tenant.ts","../src/meta/tenant-meta.ts","../src/skills.ts","../src/settings.ts","../src/fde/layout.ts","../src/fde/fingerprint.ts","../src/fde/doctor.ts","../src/fde/probe.ts","../src/fde/recon/runner.ts","../src/fde/recon/carry-over.ts","../src/fde/recon/notes.ts","../src/fde/recon/crawl.ts","../src/fde/recon/fields.ts","../src/fde/recon/gates.ts","../src/fde/recon/scc.ts","../src/fde/recon/topology.ts","../src/fde/recon/mapping.ts","../src/fde/recon/modelsmd.ts","../src/fde/recon/pointer-audit.ts","../src/fde/recon/refs.ts","../src/fde/recon/valuedomain.ts","../src/fde/gate0/aclass.ts","../src/fde/gate0/mapping-artifact.ts","../src/fde/gate0/facts.ts","../src/fde/gate0/scan.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\n\nexport interface ParsedTraceparent {\n version: string;\n traceId: string;\n spanId: string;\n flags: string;\n}\n\nexport function newTraceparent(): string {\n const traceId = randomBytes(16).toString('hex');\n const spanId = randomBytes(8).toString('hex');\n return `00-${traceId}-${spanId}-01`;\n}\n\nexport function parseTraceparent(value: string): ParsedTraceparent | null {\n const m = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/.exec(value);\n if (!m) return null;\n return { version: m[1]!, traceId: m[2]!, spanId: m[3]!, flags: m[4]! };\n}\n","import axios, { type AxiosInstance } from 'axios';\nimport { attachAuth, attachTrace, attachRetry } from './interceptors';\nimport { SDK_VERSION } from '../package-version';\n\nexport interface HttpClientOptions {\n endpoint: string;\n getAccessToken: () => Promise<string | null>;\n onRefresh: () => Promise<void>;\n timeoutMs?: number;\n userAgent?: string;\n}\n\nexport function createHttpClient(opts: HttpClientOptions): AxiosInstance {\n const client = axios.create({\n baseURL: opts.endpoint,\n timeout: opts.timeoutMs ?? 60_000,\n headers: { 'User-Agent': opts.userAgent ?? `hcmai-sdk/${SDK_VERSION}` },\n });\n attachTrace(client);\n attachAuth(client, opts.getAccessToken);\n attachRetry(client, opts.onRefresh);\n return client;\n}\n","import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios';\nimport { newTraceparent } from './trace';\n\ndeclare module 'axios' {\n export interface AxiosRequestConfig {\n /**\n * 该请求**不参与** 401 自动 refresh + retry。\n *\n * 🔴 续期请求自己必须打上它:`attachRetry` 在 401 上调 `onRefresh()`,而\n * onRefresh 最终会发一个续期请求;那个请求的 config 是新的、没有 `__retried`\n * 标记,于是它的 401 又触发一次 onRefresh —— 每层发一个新请求,**无界递归**。\n * 见 `auth/refresh.ts` 的 slidePasswordSession。\n */\n __skipAuthRetry?: boolean;\n }\n}\n\nexport function attachTrace(client: AxiosInstance): void {\n client.interceptors.request.use((cfg: InternalAxiosRequestConfig) => {\n cfg.headers.set('traceparent', newTraceparent());\n return cfg;\n });\n}\n\nexport function attachAuth(client: AxiosInstance, getToken: () => Promise<string | null>): void {\n client.interceptors.request.use(async (cfg) => {\n if (!cfg.headers.has('Authorization')) {\n const tok = await getToken();\n if (tok) cfg.headers.set('Authorization', `Bearer ${tok}`);\n }\n return cfg;\n });\n}\n\nexport function attachRetry(client: AxiosInstance, onRefresh: () => Promise<void>): void {\n client.interceptors.response.use(\n (resp) => resp,\n async (error) => {\n const status = error.response?.status;\n const cfg = error.config as (InternalAxiosRequestConfig & { __retried?: boolean }) | undefined;\n if (!cfg) throw error;\n\n // 401 单次自动 refresh + retry。__skipAuthRetry 的请求(续期请求自己)直接放行,\n // 否则 401 → onRefresh → 续期请求 → 401 会无界递归。\n if (status === 401 && !cfg.__retried && !cfg.__skipAuthRetry) {\n cfg.__retried = true;\n try {\n await onRefresh();\n } catch {\n throw error;\n }\n // 关键:删除过期 Authorization,让 attachAuth 重新取刷新后的 token\n cfg.headers.delete('Authorization');\n return client.request(cfg);\n }\n\n // 5xx 指数退避 retry 2 次\n if (status && status >= 500 && status < 600) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- 在 config 上挂 retry 计数标记\n const attempts = (cfg as any).__attempts ?? 0;\n if (attempts < 2) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- 在 config 上挂 retry 计数标记\n (cfg as any).__attempts = attempts + 1;\n const delay = 500 * Math.pow(3, attempts);\n await new Promise((r) => setTimeout(r, delay));\n // 防御性:若 token 临过期,让 attachAuth 重新取\n cfg.headers.delete('Authorization');\n return client.request(cfg);\n }\n }\n throw error;\n },\n );\n}\n","import { readFileSync } from 'node:fs';\n\ninterface PackageManifest {\n version: string;\n}\n\nconst manifest = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8'),\n) as PackageManifest;\n\nexport const SDK_VERSION = manifest.version;\n","import * as fs from 'node:fs/promises';\nimport type * as Keytar from 'keytar';\nimport { credentialsFile, profileDir, identityDir, identityMetaFile } from '../config/paths';\n\n/**\n * 凭据的来路。\n *\n * 🔴 **来路 ≠ 语义**:`password` 与 `pairing` 是两条完全不同的取得方式,但换来的是\n * **同一种东西**——`TokenManager.createToken` 铸的服务端会话 token(滑动续期、\n * 可 `/api/auth/refresh`、可 `/api/auth/logout` 单杀)。而 `pat` 与 `client_credentials`\n * 不是。判「能不能续期 / 该不该请服务端吊销」必须走 {@link isServerSlidingSession},\n * 不要拿 `=== 'password'` 去比——见该函数的说明。\n */\nexport type AuthScheme = 'client_credentials' | 'pat' | 'password' | 'pairing';\n\n/**\n * 这条凭据是不是「服务端会话 token」——即后端在每个请求上滑动续期、且\n * `/api/auth/logout` 能单条吊销的那一类。\n *\n * 🔴 **存在的理由是防一类静默退化**:全仓有三处要问这个问题——`refreshToken` 的\n * scheme 分支、`ensureSessionFresh` 的早退、`logout` 的 `revokeOnServer`。散着写成三个\n * 字面量比较,下次加第五轨时必然漏掉其中一处,而漏掉的后果**没有任何报错**:\n * - 漏在 refresh 分支 → 落到 default 抛「unknown auth scheme」,会话再也续不上\n * - 漏在 ensureSessionFresh → 不再主动续期,退回「每天重登」\n * - 漏在 revokeOnServer → **`logout` 静默跳过服务端吊销**,而 ADR-308 把\n * 「用完跑 hcm logout」写成了顾问在客户主机上那一行**唯一可操作的缓解**\n *\n * 所以这里收敛成一个谓词,三处都调它。\n */\nexport function isServerSlidingSession(scheme: AuthScheme): boolean {\n return scheme === 'password' || scheme === 'pairing';\n}\n\nexport interface TokenRecord {\n scheme: AuthScheme;\n accessTokenRef: string; // \"keychain:<svc>/<acct>\" | \"file:<path>\" | \"plain:<token>\"\n refreshTokenRef?: string;\n expiresAt: string; // ISO 8601\n scopes: string[];\n /**\n * 签发时服务端告知的 TTL(秒)。**续期后本地 expiresAt 只能靠它重算**:后端\n * `POST /api/auth/refresh` 的响应体是 `{\"message\":\"token刷新成功\"}` —— 不回新的\n * 过期时间,而它重设的 Redis TTL 用的正是签发时那个全局配置值\n * (`SecurityProperties.token.expiration`,缺省 24h)。不存这个数,续期成功之后就只能\n * 猜一个 TTL 写回本地,猜小了照样早退、猜大了会放过一个已死的 token。\n *\n * 🔴 缺省(旧记录升级上来)时按 DEFAULT_SESSION_TTL_SECONDS 兜底,见 refresh.ts。\n */\n ttlSeconds?: number;\n}\n\nconst SERVICE_PREFIX = 'hcm-cli';\ntype KeytarModule = Pick<typeof Keytar, 'deletePassword' | 'getPassword' | 'setPassword'>;\n\ninterface TokenStoreInternal {\n __identity?: boolean;\n}\n\nexport class TokenStore {\n constructor(private profile: string) {}\n\n /**\n * Factory: build an identity-scoped TokenStore. Stores meta at\n * `identities/<name>/credentials.json` and uses keychain account prefix\n * `identity-<name>` to isolate from legacy profile-scoped stores.\n *\n * Used by T13 login when writing identity meta and by T15 business\n * commands when reading the active identity's token.\n */\n static forIdentity(name: string): TokenStore {\n const ts = new TokenStore(name);\n // Freeze the sentinel so Object.assign / shallow merges can't silently flip\n // an identity store back to profile mode. A subclass `IdentityTokenStore extends\n // TokenStore` would be type-safer, but the sentinel keeps the public API one-class.\n Object.defineProperty(ts, '__identity', { value: true, writable: false, configurable: false });\n return ts;\n }\n\n private get isIdentity(): boolean {\n // Strict === true: defends against accidental non-boolean assignment via cast hole.\n return (this as TokenStoreInternal).__identity === true;\n }\n\n private metaFile(): string {\n return this.isIdentity ? identityMetaFile(this.profile) : credentialsFile(this.profile);\n }\n\n private dataDir(): string {\n return this.isIdentity ? identityDir(this.profile) : profileDir(this.profile);\n }\n\n private keychainAccountPrefix(): string {\n return this.isIdentity ? `identity-${this.profile}` : this.profile;\n }\n\n async writeMeta(record: TokenRecord): Promise<void> {\n await fs.mkdir(this.dataDir(), { recursive: true });\n await fs.writeFile(this.metaFile(), JSON.stringify(record, null, 2), { mode: 0o600 });\n }\n\n async readMeta(): Promise<TokenRecord | null> {\n try {\n const raw = await fs.readFile(this.metaFile(), 'utf-8');\n return JSON.parse(raw) as TokenRecord;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw e;\n }\n }\n\n async setAccessToken(token: string): Promise<string> {\n if (await this.canUseKeychain()) {\n const keytar = await loadKeytar();\n if (!keytar) return this.writeTokenFile('access', token);\n const account = `${this.keychainAccountPrefix()}/access`;\n await keytar.setPassword(SERVICE_PREFIX, account, token);\n return `keychain:${SERVICE_PREFIX}/${account}`;\n }\n // fallback: 0600 文件\n return this.writeTokenFile('access', token);\n }\n\n async setRefreshToken(token: string): Promise<string> {\n if (await this.canUseKeychain()) {\n const keytar = await loadKeytar();\n if (!keytar) return this.writeTokenFile('refresh', token);\n const account = `${this.keychainAccountPrefix()}/refresh`;\n await keytar.setPassword(SERVICE_PREFIX, account, token);\n return `keychain:${SERVICE_PREFIX}/${account}`;\n }\n return this.writeTokenFile('refresh', token);\n }\n\n async getAccessToken(): Promise<string | null> {\n // 1. env override\n if (process.env.HCM_PAT) return process.env.HCM_PAT;\n if (process.env.HCM_ACCESS_TOKEN) return process.env.HCM_ACCESS_TOKEN;\n // 2. read meta + resolve ref\n const meta = await this.readMeta();\n if (!meta) return null;\n return this.resolveRef(meta.accessTokenRef);\n }\n\n async getRefreshToken(): Promise<string | null> {\n const meta = await this.readMeta();\n if (!meta?.refreshTokenRef) return null;\n return this.resolveRef(meta.refreshTokenRef);\n }\n\n async clear(): Promise<void> {\n const meta = await this.readMeta();\n if (!meta) return;\n // accessTokenRef may be absent on stub identities (created before first login)\n if (meta.accessTokenRef?.startsWith('keychain:')) {\n const account = meta.accessTokenRef\n .slice('keychain:'.length)\n .split('/')\n .slice(1)\n .join('/');\n const keytar = await loadKeytar();\n if (keytar) await keytar.deletePassword(SERVICE_PREFIX, account);\n } else if (meta.accessTokenRef?.startsWith('file:')) {\n await fs.rm(meta.accessTokenRef.slice('file:'.length), { force: true });\n }\n if (meta.refreshTokenRef?.startsWith('keychain:')) {\n const account = meta.refreshTokenRef\n .slice('keychain:'.length)\n .split('/')\n .slice(1)\n .join('/');\n const keytar = await loadKeytar();\n if (keytar) await keytar.deletePassword(SERVICE_PREFIX, account);\n } else if (meta.refreshTokenRef?.startsWith('file:')) {\n await fs.rm(meta.refreshTokenRef.slice('file:'.length), { force: true });\n }\n // Profile-scoped stores own the meta file (legacy logout) → delete it.\n // Identity-scoped stores share the meta file with deleteIdentity (T11\n // cascade ownership boundary) — leave it for deleteIdentity to clean up.\n if (!this.isIdentity) {\n await fs.rm(this.metaFile(), { force: true });\n }\n }\n\n private async resolveRef(ref: string): Promise<string | null> {\n if (ref.startsWith('keychain:')) {\n const account = ref.slice('keychain:'.length).split('/').slice(1).join('/');\n const keytar = await loadKeytar();\n if (!keytar) return null;\n return keytar.getPassword(SERVICE_PREFIX, account);\n }\n if (ref.startsWith('file:')) {\n try {\n return await fs.readFile(ref.slice('file:'.length), 'utf-8');\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw e;\n }\n }\n if (ref.startsWith('plain:')) return ref.slice('plain:'.length);\n return null;\n }\n\n private async canUseKeychain(): Promise<boolean> {\n const keytar = await loadKeytar();\n if (!keytar) return false;\n try {\n // sanity probe\n await keytar.getPassword(SERVICE_PREFIX, '__probe__');\n return true;\n } catch {\n return false;\n }\n }\n\n private async writeTokenFile(kind: 'access' | 'refresh', token: string): Promise<string> {\n const file = `${this.metaFile()}.${kind}`;\n await fs.mkdir(this.dataDir(), { recursive: true });\n await fs.writeFile(file, token, { mode: 0o600 });\n if (kind === 'access') {\n // eslint-disable-next-line no-console\n console.warn(`[hcm] WARNING: OS keychain unavailable. Token saved to ${file} (0600).`);\n }\n return `file:${file}`;\n }\n}\n\nasync function loadKeytar(): Promise<KeytarModule | null> {\n try {\n const mod = (await import('keytar')) as unknown as KeytarModule & {\n default?: KeytarModule;\n };\n return mod.default ?? mod;\n } catch {\n return null;\n }\n}\n","import * as path from 'node:path';\nimport * as os from 'node:os';\n\nexport function hcmConfigDir(): string {\n const xdg = process.env.XDG_CONFIG_HOME;\n if (xdg) return path.join(xdg, 'hcm');\n const home = process.env.HOME ?? os.homedir();\n return path.join(home, '.config', 'hcm');\n}\n\nexport function profileDir(profile: string): string {\n return path.join(hcmConfigDir(), profile);\n}\n\nexport function profileFile(profile: string): string {\n return path.join(profileDir(profile), 'profile.yml');\n}\n\nexport function credentialsFile(profile: string): string {\n return path.join(profileDir(profile), 'credentials.json');\n}\n\nexport function globalConfigFile(): string {\n return path.join(hcmConfigDir(), 'config.yml');\n}\n\nexport function conversationStateFile(profile: string): string {\n return path.join(profileDir(profile), 'last-conversation.json');\n}\n\nexport function replHistoryFile(profile: string): string {\n return path.join(profileDir(profile), 'repl-history');\n}\n\nexport function envsDir(): string {\n return path.join(hcmConfigDir(), 'envs');\n}\n\nexport function envDir(name: string): string {\n return path.join(envsDir(), name);\n}\n\nexport function envFile(name: string): string {\n return path.join(envDir(name), 'env.yml');\n}\n\nexport function identitiesDir(): string {\n return path.join(hcmConfigDir(), 'identities');\n}\n\nexport function identityDir(name: string): string {\n return path.join(identitiesDir(), name);\n}\n\nexport function identityMetaFile(name: string): string {\n return path.join(identityDir(name), 'credentials.json');\n}\n\nexport function archiveDir(): string {\n return path.join(hcmConfigDir(), '_archive');\n}\n","import type { TokenRecord } from './token-store';\n\n/**\n * **服务端认定的 token 归属**——与\"调用方以为的归属\"是两回事,两者不一致时\n * 一切看起来都正常,而你在对着另一个租户干活。\n *\n * 这正是 fde-kit 那边 Ruling 30 防的同一件事:`hcm login --tenant 1` 传进去的是\n * 「我要的租户」,服务端回的 `user.tenantId` 才是「token 实际归属的租户」。此前\n * login 成功那行只回显前者,于是登错租户是静默的。\n */\nexport interface Principal {\n /** 服务端认定的租户。ADR-177 之后新租户是 UUID,别按 int 处理。 */\n tenantId?: string;\n username?: string;\n roles?: string[];\n}\n\n/** 登录helper 的统一返回:凭据元数据 + 服务端认定的归属(拿不到时 undefined)。 */\nexport interface LoginOutcome {\n record: TokenRecord;\n /**\n * 🔴 `undefined` = **服务端没在响应里给身份**,与「给了但字段为空」是两个状态。\n * 前者的下一步是问服务端为什么不给,后者的下一步是看那个账号本身。合并之后\n * 回显只能给出一句放之四海而皆准的废话。\n */\n principal?: Principal;\n}\n\n/**\n * 从服务端响应里的 user/userInfo 对象抽出 Principal。\n *\n * 两个来源字段名一致但形状不同,所以这里只认键、不认容器:\n * - `POST /api/auth/login` → `LoginResponse.user`(Map,含 id/username/tenantId/roles)\n * - `GET /api/auth/info` → `UserInfoResponse`(含 userId/username/tenantId/roles)\n *\n * 全部字段都当可选处理:任何一个缺失都不该让登录失败——登录已经成功了,\n * 这里只是回显。\n */\nexport function toPrincipal(raw: unknown): Principal | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const o = raw as Record<string, unknown>;\n const principal: Principal = {};\n // tenantId 可能是 number(旧租户 id=1) 或 string(UUID),统一成 string 展示。\n if (o.tenantId !== undefined && o.tenantId !== null) principal.tenantId = String(o.tenantId);\n if (typeof o.username === 'string' && o.username) principal.username = o.username;\n if (Array.isArray(o.roles)) principal.roles = o.roles.map(String);\n return Object.keys(principal).length > 0 ? principal : undefined;\n}\n\n/** 回显用的一行摘要。`principal` 为空时明说「服务端未回身份」,不静默略过。 */\nexport function describePrincipal(principal: Principal | undefined): string {\n if (!principal) return 'server-identity=<未回身份>';\n const parts: string[] = [];\n parts.push(`tenant=${principal.tenantId ?? '<未回>'}`);\n parts.push(`user=${principal.username ?? '<未回>'}`);\n if (principal.roles?.length) parts.push(`roles=${principal.roles.join(',')}`);\n return parts.join(' ');\n}\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, type TokenRecord } from './token-store';\nimport { toPrincipal, type LoginOutcome } from './principal';\n\nexport interface PasswordLoginInput {\n endpoint: string;\n tenantId: string;\n username: string;\n password: string;\n profile: string;\n httpClient: AxiosInstance;\n}\n\n/** 后端 `SecurityProperties.token.expiration` 的缺省值(24h),仅在服务端没回 expiresIn 时兜底。 */\nexport const DEFAULT_SESSION_TTL_SECONDS = 24 * 60 * 60;\n\nexport async function loginPassword(input: PasswordLoginInput): Promise<LoginOutcome> {\n const resp = await input.httpClient.post(\n `${input.endpoint}/api/auth/login`,\n {\n tenantId: input.tenantId,\n username: input.username,\n password: input.password,\n },\n { headers: { 'Content-Type': 'application/json' } },\n );\n const { token, expiresIn, user } = resp.data;\n if (!token) {\n throw new Error('Login response missing token');\n }\n\n const ttlSeconds = expiresIn ?? DEFAULT_SESSION_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();\n\n const store = new TokenStore(input.profile);\n const accessTokenRef = await store.setAccessToken(token);\n\n const record: TokenRecord = {\n scheme: 'password',\n accessTokenRef,\n // 口令会话**没有** refresh token —— 后端 `/api/auth/login` 只回 token。\n // 它的续期语义是「同一个 token 被服务端重设 TTL」,见 refresh.ts。\n refreshTokenRef: undefined,\n expiresAt,\n scopes: ['*'],\n ttlSeconds,\n };\n await store.writeMeta(record);\n return { record, principal: toPrincipal(user) };\n}\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, isServerSlidingSession, type TokenRecord } from './token-store';\nimport { DEFAULT_SESSION_TTL_SECONDS } from './password';\n\nexport interface RefreshInput {\n endpoint: string;\n /**\n * Identity name (preferred). When set, uses TokenStore.forIdentity(identity).\n * Required for sessions established via HcmClient.fromAuthContext.\n */\n identity?: string;\n /**\n * @deprecated Profile name (legacy). Used when identity is not provided —\n * for backward compat with fromConfig callers and tests pre-T15.\n */\n profile?: string;\n httpClient: AxiosInstance;\n}\n\n/**\n * 本地在「还剩这么多」时就主动去续期。\n *\n * 🔴 这个值**不需要、也不应该**去对齐服务端的 `SecurityProperties.token.refreshThreshold`\n * (缺省 2h):那是服务端**自动**续期的触发线,而我们打的 `/api/auth/refresh` 是\n * **无条件**续期(只要 Redis key 还在就重设 TTL),跟阈值无关。抄一份服务端常量到这里\n * 只会多一个会漂的载体。\n */\nexport const PROACTIVE_REFRESH_MARGIN_SECONDS = 60 * 60;\n\n/**\n * 续期。**三种 scheme 是三套完全不同的服务端语义,不是一个端点能覆盖的。**\n *\n * 🔴 此前这里写成了一条,于是三条全错,而且错得不会被发现——口令会话在\n * 「取 refresh token」那一步就抛了,请求根本没发出去,后面那段对不上的协议\n * 因此永远跑不到,测试也只覆盖了这些提前抛出的错误路径。三条的真实形态:\n *\n * - **password**:`POST /api/auth/refresh`,认 **Authorization 头**,服务端把\n * 同一个 token 的 Redis TTL 重设回 expiration,响应体只有\n * `{\"message\":\"token刷新成功\"}`——**没有 access_token**。旧实现往 body 塞\n * `{refresh_token}` 再读 `resp.data.access_token`,两头都对不上;而口令登录\n * 压根拿不到 refresh token,连这个请求都发不出去。\n * - **client_credentials**:走 `POST /api/oauth/token` + `grant_type=refresh_token`,\n * 且后端在进 switch 之前就强制校验 `client_id` + `client_secret`。本 CLI\n * **不保存 client_secret**(有意为之:那是长期凭据) ⇒ 这条路在当前设计下\n * 不可能执行成功。所以这里**明说要重登**,而不是发一个注定 400 的请求\n * 再把它包装成一句看不懂的错误。\n * - **pat**:本来就不可续期,由后端管失效。\n */\nexport async function refreshToken(input: RefreshInput): Promise<TokenRecord> {\n if (!input.identity && !input.profile) {\n throw new Error('refreshToken: either identity or profile required');\n }\n const store = input.identity\n ? TokenStore.forIdentity(input.identity)\n : new TokenStore(input.profile!);\n const meta = await store.readMeta();\n if (!meta) throw new Error('no credentials to refresh');\n\n switch (meta.scheme) {\n case 'pat':\n throw new Error('PAT cannot be refreshed; re-run `hcm login --pat-stdin`');\n case 'client_credentials':\n throw new Error(\n 'client_credentials 会话无法自动续期:刷新需要 client_secret,而 CLI 不保存它。' +\n '重新执行 `hcm login --client-id <id>`',\n );\n case 'password':\n case 'pairing':\n // 配对码换来的就是 TokenManager 铸的普通会话 token,与口令登录**同一种东西**,\n // 续期语义完全一致(ADR-308)。\n return slidePasswordSession(store, meta, input);\n default: {\n // scheme 是联合类型,走到这里说明磁盘上的记录来自更新版本的 CLI。\n const unknown: never = meta.scheme;\n throw new Error(`unknown auth scheme in stored credentials: ${String(unknown)}`);\n }\n }\n}\n\n/**\n * 口令会话续期:打一次 `/api/auth/refresh`,成功后把本地过期时间往后推 ttlSeconds。\n *\n * 🔴 `__skipAuthRetry` 不是可选的防御性代码:`attachRetry` 在 401 上会调\n * `onRefresh()`,而 onRefresh 最终就走到这里。如果这个请求自己也 401(token 真的\n * 死了),它的 config 是**新的**、没有 `__retried` 标记,于是又触发一次 onRefresh —— \n * 每一层都发一个新请求,**无界递归**。此前这条路被提前抛出的错误挡着从没跑到过,\n * 一旦让它真发请求,递归就成立了。\n */\nasync function slidePasswordSession(\n store: TokenStore,\n meta: TokenRecord,\n input: RefreshInput,\n): Promise<TokenRecord> {\n const accessToken = await store.getAccessToken();\n if (!accessToken) {\n throw new Error('no access token to refresh; re-run `hcm login`');\n }\n\n await input.httpClient.post(`${input.endpoint}/api/auth/refresh`, undefined, {\n headers: { Authorization: `Bearer ${accessToken}` },\n // 见上:阻断 401 → onRefresh → 本函数 → 401 的递归。\n __skipAuthRetry: true,\n });\n\n // 服务端不回新的过期时间,重设的 TTL 就是签发时那个值。\n const ttlSeconds = meta.ttlSeconds ?? DEFAULT_SESSION_TTL_SECONDS;\n const record: TokenRecord = {\n ...meta,\n // token 本身没变,accessTokenRef / scopes 原样保留。\n expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),\n ttlSeconds,\n };\n await store.writeMeta(record);\n return record;\n}\n\nexport function needsRefresh(meta: TokenRecord, marginSec = 60): boolean {\n const expiresAtMs = new Date(meta.expiresAt).getTime();\n return Date.now() + marginSec * 1000 > expiresAtMs;\n}\n\n/**\n * 命令开跑前把会话续上——**这才是\"登录一次基本不用再输密码\"落地的那一处**。\n *\n * 背景:后端 `TokenAuthenticationFilter` 在每个带 token 的请求上都会滑动续期\n * (剩余 <= refreshThreshold 时把 Redis TTL 重设回 24h),也就是说**只要你在用,\n * 服务端的会话就不会死**。而 CLI 这边的 `expiresAt` 是登录那一刻算死的静态值、\n * 之后再没更新过,`ensureActiveAuth` 拿它一比就抛 TOKEN_EXPIRED —— 结果是\n * **CLI 在拒绝服务端明明还认的 token**。\n *\n * 这里做的事:本地剩余时间进入窗口时主动打一次无条件续期端点,并把服务端那次\n * 续期的结果**写回本地**,让本地的过期判断重新变成真的。\n *\n * 返回是否真的续了。**任何失败都不抛**:续期是尽力而为,失败时后面的请求自己会\n * 401 并给出该给的引导;为了一次续期失败就让整条命令挂掉是本末倒置。\n */\nexport async function ensureSessionFresh(input: RefreshInput): Promise<boolean> {\n try {\n const store = input.identity\n ? TokenStore.forIdentity(input.identity)\n : new TokenStore(input.profile!);\n const meta = await store.readMeta();\n // 只有服务端会话 token 是「服务端滑动同一个 token」这套语义。PAT 由后端管失效,\n // client_credentials 拿不到 secret 续不了——两者在这里都该原样放过。\n // 🔴 走谓词而不是 `=== 'password'`:见 isServerSlidingSession 的说明。\n if (!meta || !isServerSlidingSession(meta.scheme)) return false;\n if (!needsRefresh(meta, PROACTIVE_REFRESH_MARGIN_SECONDS)) return false;\n await refreshToken(input);\n return true;\n } catch {\n return false;\n }\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport yaml from 'js-yaml';\nimport { hcmConfigDir, profileDir, profileFile } from './paths';\n\nexport interface ProfileConfig {\n endpoint: string;\n tenantId: string;\n defaultAgent?: string;\n displayName?: string;\n}\n\nexport async function loadProfile(profile: string): Promise<ProfileConfig> {\n const file = profileFile(profile);\n let content: string;\n try {\n content = await fs.readFile(file, 'utf-8');\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`profile '${profile}' not found at ${file}`);\n }\n throw e;\n }\n return yaml.load(content) as ProfileConfig;\n}\n\nexport async function saveProfile(profile: string, cfg: ProfileConfig): Promise<void> {\n await fs.mkdir(profileDir(profile), { recursive: true });\n await fs.writeFile(profileFile(profile), yaml.dump(cfg), 'utf-8');\n}\n\nexport async function listProfiles(): Promise<string[]> {\n const root = hcmConfigDir();\n try {\n const entries = await fs.readdir(root, { withFileTypes: true });\n const result: string[] = [];\n for (const e of entries) {\n if (e.isDirectory()) {\n try {\n await fs.access(path.join(root, e.name, 'profile.yml'));\n result.push(e.name);\n } catch {\n /* skip */\n }\n }\n }\n return result;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw e;\n }\n}\n","import * as fs from 'node:fs/promises';\nimport yaml from 'js-yaml';\nimport { globalConfigFile, hcmConfigDir } from './paths';\n\nexport interface GlobalConfig {\n /** Canonical: name of the active environment. */\n activeEnv: string;\n /**\n * @deprecated Use `activeEnv`. Kept in sync with `activeEnv` for backward compat\n * while in-tree callers migrate. Will be removed at end of Phase 2.\n */\n activeProfile?: string;\n defaults?: {\n output?: 'table' | 'json' | 'yaml';\n timeout?: string;\n };\n}\n\ninterface RawShape {\n activeProfile?: string;\n activeEnv?: string;\n defaults?: GlobalConfig['defaults'];\n}\n\nconst DEFAULT_ENV = 'default';\nconst DEFAULT_DEFAULTS = { output: 'table' as const, timeout: '60s' };\n\nexport async function loadGlobalConfig(): Promise<GlobalConfig> {\n let raw: RawShape = {};\n try {\n const content = await fs.readFile(globalConfigFile(), 'utf-8');\n raw = (yaml.load(content) as RawShape) ?? {};\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e;\n }\n const activeEnv = raw.activeEnv ?? raw.activeProfile ?? DEFAULT_ENV;\n return {\n activeEnv,\n activeProfile: activeEnv, // dual-write alias\n defaults: { ...DEFAULT_DEFAULTS, ...raw.defaults },\n };\n}\n\nexport async function saveGlobalConfig(cfg: GlobalConfig): Promise<void> {\n await fs.mkdir(hcmConfigDir(), { recursive: true });\n // Canonical wins. Legacy callers that still mutate `activeProfile` must be\n // migrated to assign `activeEnv` instead — done in T5 for the 3 mutation\n // sites (config.ts × 2, login.ts × 1). Read-only callers stay safe via the\n // dual-write alias from `loadGlobalConfig`.\n const activeEnv = cfg.activeEnv ?? cfg.activeProfile ?? DEFAULT_ENV;\n const out = {\n activeEnv,\n activeProfile: activeEnv,\n defaults: cfg.defaults,\n };\n await fs.writeFile(globalConfigFile(), yaml.dump(out), 'utf-8');\n}\n\n/**\n * Resolve active env name. Priority: --env flag > HCM_ENV > HCM_PROFILE (legacy) > global.activeEnv\n */\nexport function resolveActiveEnv(\n args: { envFlag?: string },\n env: NodeJS.ProcessEnv,\n global: GlobalConfig,\n): string {\n return args.envFlag ?? env.HCM_ENV ?? env.HCM_PROFILE ?? global.activeEnv;\n}\n\n/**\n * @deprecated Use `resolveActiveEnv`. Keeps old signature `(args: {flag})` for backward compat.\n * Will be removed at end of Phase 2.\n */\nexport function resolveActiveProfile(\n args: { flag?: string },\n env: NodeJS.ProcessEnv,\n global: GlobalConfig,\n): string {\n return resolveActiveEnv({ envFlag: args.flag }, env, global);\n}\n","import * as fs from 'node:fs/promises';\nimport yaml from 'js-yaml';\nimport { envDir, envFile, envsDir } from './paths';\nimport { CliError, CliErrorCode } from '../errors';\n\nexport interface EnvConfig {\n endpoint: string;\n frontend?: string;\n tenantId?: string;\n defaultIdentity?: string;\n defaultAgent?: string;\n}\n\nexport async function saveEnv(name: string, cfg: EnvConfig): Promise<void> {\n if (!cfg.endpoint) {\n throw new CliError({\n code: CliErrorCode.ENV_ENDPOINT_REQUIRED,\n message: `env '${name}' requires endpoint`,\n });\n }\n await fs.mkdir(envDir(name), { recursive: true });\n await fs.writeFile(envFile(name), yaml.dump(cfg), 'utf-8');\n}\n\nexport async function loadEnv(name: string): Promise<EnvConfig> {\n try {\n const content = await fs.readFile(envFile(name), 'utf-8');\n return yaml.load(content) as EnvConfig;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new CliError({\n code: CliErrorCode.ENV_NOT_FOUND,\n message: `env '${name}' not found. Run: hcm env list`,\n });\n }\n throw e;\n }\n}\n\nexport async function listEnvs(): Promise<string[]> {\n try {\n const entries = await fs.readdir(envsDir(), { withFileTypes: true });\n const result: string[] = [];\n for (const e of entries) {\n if (!e.isDirectory()) continue;\n try {\n await fs.access(envFile(e.name));\n result.push(e.name);\n } catch {\n /* skip */\n }\n }\n return result;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw e;\n }\n}\n\nexport async function deleteEnv(name: string): Promise<void> {\n try {\n await fs.access(envFile(name));\n } catch {\n throw new CliError({\n code: CliErrorCode.ENV_NOT_FOUND,\n message: `env '${name}' not found`,\n });\n }\n await fs.rm(envDir(name), { recursive: true, force: true });\n}\n","export enum ExitCode {\n SUCCESS = 0,\n USAGE_ERROR = 1,\n AUTH_ERROR = 2,\n BUSINESS_ERROR = 3,\n NETWORK_ERROR = 4,\n CONFIG_ERROR = 5,\n SIGINT = 130,\n}\n\nexport enum CliErrorCode {\n // usage (exit 1)\n INVALID_ARGUMENT = 'INVALID_ARGUMENT',\n INVALID_JSON = 'INVALID_JSON',\n MISSING_FLAG = 'MISSING_FLAG',\n\n // auth (exit 2)\n AUTH_FAILED = 'AUTH_FAILED',\n AUTH_EXPIRED = 'AUTH_EXPIRED',\n AUTH_REFRESH_FAILED = 'AUTH_REFRESH_FAILED',\n WS_AUTH_ERROR = 'WS_AUTH_ERROR',\n SCOPE_FORBIDDEN = 'SCOPE_FORBIDDEN',\n\n // business (exit 3)\n BUSINESS_RULE_VIOLATION = 'BUSINESS_RULE_VIOLATION',\n MODEL_NOT_FOUND = 'MODEL_NOT_FOUND',\n ACTION_NOT_FOUND = 'ACTION_NOT_FOUND',\n AGENT_NOT_FOUND = 'AGENT_NOT_FOUND',\n TOOL_TIMEOUT = 'TOOL_TIMEOUT',\n\n // network (exit 4)\n NETWORK_ERROR = 'NETWORK_ERROR',\n TIMEOUT = 'TIMEOUT',\n SERVER_ERROR = 'SERVER_ERROR',\n PROTOCOL_UNSUPPORTED = 'PROTOCOL_UNSUPPORTED',\n\n // config (exit 5)\n PROFILE_NOT_FOUND = 'PROFILE_NOT_FOUND',\n CONFIG_PARSE_ERROR = 'CONFIG_PARSE_ERROR',\n KEYCHAIN_UNAVAILABLE = 'KEYCHAIN_UNAVAILABLE',\n\n // env + identity (exit 5/2)\n ENV_NOT_FOUND = 'ENV_NOT_FOUND',\n ENV_ALREADY_EXISTS = 'ENV_ALREADY_EXISTS',\n ENV_ENDPOINT_REQUIRED = 'ENV_ENDPOINT_REQUIRED',\n IDENTITY_NOT_FOUND = 'IDENTITY_NOT_FOUND',\n IDENTITY_ALREADY_EXISTS = 'IDENTITY_ALREADY_EXISTS',\n IDENTITY_NOT_IN_ENV = 'IDENTITY_NOT_IN_ENV',\n TOKEN_EXPIRED = 'TOKEN_EXPIRED',\n NO_ACTIVE_ENV = 'NO_ACTIVE_ENV',\n\n // skills(exit 1/3/4)\n /** 同一个 id 在 CLI 自带与睿戎目录里同时存在。确定性拒绝,不静默取其一。 */\n SKILL_ID_CONFLICT = 'SKILL_ID_CONFLICT',\n /** 装在这份 CLI 里的技能资源读不到(dist/skills 缺失或损坏)。安装损坏,硬失败。 */\n SKILL_SOURCE_UNREACHABLE = 'SKILL_SOURCE_UNREACHABLE',\n /** `skills sync` 的锁文件此刻只记得下睿戎来源的出处;自带技能没有它要的三个字段。 */\n SYNC_SOURCE_UNSUPPORTED = 'SYNC_SOURCE_UNSUPPORTED',\n}\n\nconst CODE_TO_EXIT: Record<CliErrorCode, ExitCode> = {\n [CliErrorCode.INVALID_ARGUMENT]: ExitCode.USAGE_ERROR,\n [CliErrorCode.INVALID_JSON]: ExitCode.USAGE_ERROR,\n [CliErrorCode.MISSING_FLAG]: ExitCode.USAGE_ERROR,\n [CliErrorCode.AUTH_FAILED]: ExitCode.AUTH_ERROR,\n [CliErrorCode.AUTH_EXPIRED]: ExitCode.AUTH_ERROR,\n [CliErrorCode.AUTH_REFRESH_FAILED]: ExitCode.AUTH_ERROR,\n [CliErrorCode.WS_AUTH_ERROR]: ExitCode.AUTH_ERROR,\n [CliErrorCode.SCOPE_FORBIDDEN]: ExitCode.AUTH_ERROR,\n [CliErrorCode.BUSINESS_RULE_VIOLATION]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.MODEL_NOT_FOUND]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.ACTION_NOT_FOUND]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.AGENT_NOT_FOUND]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.TOOL_TIMEOUT]: ExitCode.BUSINESS_ERROR,\n [CliErrorCode.NETWORK_ERROR]: ExitCode.NETWORK_ERROR,\n [CliErrorCode.TIMEOUT]: ExitCode.NETWORK_ERROR,\n [CliErrorCode.SERVER_ERROR]: ExitCode.NETWORK_ERROR,\n [CliErrorCode.PROTOCOL_UNSUPPORTED]: ExitCode.NETWORK_ERROR,\n [CliErrorCode.PROFILE_NOT_FOUND]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.CONFIG_PARSE_ERROR]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.KEYCHAIN_UNAVAILABLE]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.ENV_NOT_FOUND]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.ENV_ALREADY_EXISTS]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.ENV_ENDPOINT_REQUIRED]: ExitCode.USAGE_ERROR,\n [CliErrorCode.IDENTITY_NOT_FOUND]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.IDENTITY_ALREADY_EXISTS]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.IDENTITY_NOT_IN_ENV]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.TOKEN_EXPIRED]: ExitCode.AUTH_ERROR,\n [CliErrorCode.NO_ACTIVE_ENV]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.SKILL_ID_CONFLICT]: ExitCode.BUSINESS_ERROR,\n // 🔴 装坏了不是网络问题:报 network 会让顾问先去查 VPN。\n [CliErrorCode.SKILL_SOURCE_UNREACHABLE]: ExitCode.CONFIG_ERROR,\n [CliErrorCode.SYNC_SOURCE_UNSUPPORTED]: ExitCode.USAGE_ERROR,\n};\n\n/**\n * 🔴 **兜底不是防御性编程,是防「静默退 0」**。\n *\n * `CODE_TO_EXIT` 是 `Record<CliErrorCode, ExitCode>`,编译期漏一条会红,\n * 所以这一句在类型正确的构建里永远走不到。它防的是**构建不同步**:\n * CLI 的 dist 认识一个新错误码、而 sdk 的 dist 还是旧的时候,\n * 这里会取到 `undefined` ⇒ `process.exit(undefined)` ⇒ **报了错却退 0**。\n * 实测撞到过一次(只跑了 `npm run build -w @hcmai/cli`,没重建 sdk):\n * 终端上明明打印了 Error,脚本里 `$?` 是 0——上游调用方会当成成功。\n */\nexport function exitCodeFor(code: CliErrorCode): ExitCode {\n return CODE_TO_EXIT[code] ?? ExitCode.USAGE_ERROR;\n}\n","import { CliErrorCode, exitCodeFor } from './codes';\nimport type { ExitCode } from './codes';\n\nexport interface CliErrorInit {\n code: CliErrorCode;\n message: string;\n context?: Record<string, unknown>;\n traceId?: string;\n profile?: string;\n cause?: unknown;\n}\n\nexport class CliError extends Error {\n readonly code: CliErrorCode;\n readonly exitCode: ExitCode;\n readonly context?: Record<string, unknown>;\n readonly traceId?: string;\n readonly profile?: string;\n override readonly cause?: unknown;\n\n constructor(init: CliErrorInit) {\n super(init.message);\n this.name = 'CliError';\n this.code = init.code;\n this.exitCode = exitCodeFor(init.code);\n this.context = init.context;\n this.traceId = init.traceId;\n this.profile = init.profile;\n this.cause = init.cause;\n }\n\n toFormattedString(): string {\n const parts: string[] = [`${this.code}: ${this.message}`];\n // CliErrorCode 是命令行自己的分类(决定 exit code),后端错误码比它精确得多\n // (FILTER_SYNTAX_ERROR vs 一律降级成的 BUSINESS_RULE_VIOLATION)。两者不一致时都要显示,\n // 只显示前者就是在报一个错的错误码。\n const serverCode = this.context?.responseCode;\n if (typeof serverCode === 'string' && serverCode && serverCode !== this.code) {\n parts.push(`[服务端错误码: ${serverCode}]`);\n }\n const field = this.context?.field;\n if (typeof field === 'string' && field) parts.push(`[字段: ${field}]`);\n if (this.traceId) parts.push(`[traceId: ${this.traceId}]`);\n if (this.profile) parts.push(`[profile: ${this.profile}]`);\n let out = parts.join(' ');\n // 🔴 details 必须显示。后端把 BusinessException 的 description 放进 details\n // (CommonErrorResponse:data 为空时 details 就是 description),那句话往往是**唯一**\n // 说清「哪里错了、该改成什么」的文本,而 message 常常是错误码的静态措辞。\n // fromAxiosError 早就把它解析进 context 了,却一直没渲染出来——2026-08-29 顾问仿真\n // 实测:静态 JSON 数据源传了标量,服务端回的是「json 数据源 data 字段必须是 List 或\n // Map」,命令行只显示 BUSINESS_RULE_VIOLATION,Agent 无从自纠,整轮验收被迫中止。\n const details = this.context?.details;\n const detailText = formatDetails(details);\n if (detailText) out += `\\n 详情: ${detailText}`;\n const nextStep = this.context?.nextStep;\n if (typeof nextStep === 'string' && nextStep) out += `\\n 下一步: ${nextStep}`;\n return out;\n }\n\n toJSON() {\n return {\n code: this.code,\n message: this.message,\n context: this.context,\n traceId: this.traceId,\n };\n }\n}\n\n// axios doesn't export a clean error type usable here; intentional any\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function fromAxiosError(err: any, profile?: string): CliError {\n const status = err.response?.status;\n const traceId = err.response?.headers?.['x-trace-id'];\n\n // 🔴 HCM 的错误信封是嵌套的:{ \"error\": { code, message, field, details, nextStep, retryable } }\n // —— 见后端 CommonErrorResponse / ActionError。以前这里读的是顶层 data.code / data.message,\n // 对真实响应**恒取不到**:消息落回 axios 的 \"Request failed with status code 400\",\n // 错误码一律按 HTTP 状态码降级,连带下面 MODEL_NOT_FOUND / ACTION_NOT_FOUND 两条特判成了死代码。\n // 单测当时全绿,因为它们喂的是平铺形状 —— 围着 bug 建的 fixture 证明不了任何事。\n // 顶层留作回退:少数早期端点确实平铺返回。\n const data = err.response?.data;\n const body = (data?.error ?? data) as Record<string, unknown> | undefined;\n const respCode = body?.code as string | undefined;\n const respMsg = body?.message as string | undefined;\n const respField = body?.field as string | undefined;\n const respDetails = body?.details;\n const respNextStep = body?.nextStep as string | undefined;\n const respRetryable = body?.retryable as boolean | undefined;\n\n let code: CliErrorCode;\n if (status === 401) code = CliErrorCode.AUTH_FAILED;\n else if (status === 403) code = CliErrorCode.SCOPE_FORBIDDEN;\n else if (status === 404 && respCode === 'MODEL_NOT_FOUND') code = CliErrorCode.MODEL_NOT_FOUND;\n else if (status === 404 && respCode === 'ACTION_NOT_FOUND') code = CliErrorCode.ACTION_NOT_FOUND;\n else if (status && status >= 400 && status < 500) code = CliErrorCode.BUSINESS_RULE_VIOLATION;\n else if (status && status >= 500) code = CliErrorCode.SERVER_ERROR;\n else if (err.code === 'ECONNABORTED') code = CliErrorCode.TIMEOUT;\n else code = CliErrorCode.NETWORK_ERROR;\n\n return new CliError({\n code,\n message: respMsg ?? err.message ?? 'request failed',\n context: {\n status,\n responseCode: respCode,\n // 结构化错误的这几件必须原样带出:field 指哪个字段错了、details 机器可读、\n // nextStep 是后端给的路标(ADR-166 终局协议)—— 吞掉它等于让命令行用户自己猜下一步。\n ...(respField ? { field: respField } : {}),\n ...(respDetails !== undefined ? { details: respDetails } : {}),\n ...(respNextStep ? { nextStep: respNextStep } : {}),\n ...(respRetryable !== undefined ? { retryable: respRetryable } : {}),\n },\n traceId,\n profile,\n cause: err,\n });\n}\n\n\n/**\n * details 可能是字符串(后端 description 回退路径),也可能是结构化 payload。\n * 字符串原样显示;结构化的紧凑序列化,超长截断——宁可截断也不要整段不显示。\n */\nfunction formatDetails(details: unknown): string | undefined {\n if (details === undefined || details === null) return undefined;\n if (typeof details === 'string') return details.trim() || undefined;\n if (typeof details === 'number' || typeof details === 'boolean') return String(details);\n let text: string;\n try {\n text = JSON.stringify(details);\n } catch {\n return undefined;\n }\n if (!text || text === '{}' || text === '[]') return undefined;\n return text.length > 800 ? `${text.slice(0, 800)}…(已截断)` : text;\n}\n","import * as fs from 'node:fs/promises';\nimport { identityDir, identityMetaFile, identitiesDir, envFile } from './paths';\nimport { CliError, CliErrorCode } from '../errors';\nimport type { AuthScheme } from '../auth/token-store';\n\nexport interface IdentityMeta {\n env: string;\n scheme: AuthScheme;\n scopes: string[];\n expiresAt?: string;\n username?: string;\n /**\n * keychain ref same as TokenRecord.accessTokenRef. Populated by TokenStore.forIdentity.\n * Optional here because saveIdentity may be called before token is written.\n */\n accessTokenRef?: string;\n refreshTokenRef?: string;\n /**\n * **服务端认定的租户**(登录响应里的 `user.tenantId` / `/api/auth/info` 的 tenantId),\n * 不是命令行上传进去的那个。两者不一致 = 你在对着另一个租户干活,而一切看起来都正常。\n *\n * 命令执行时优先用它,`EnvConfig.tenantId` 只是登录前的入参与兜底 —— 后者缺省是\n * 写死的 `'1'`,而 ADR-177 之后新租户是 UUID,那个缺省几乎必然是错的。\n */\n tenantId?: string;\n /**\n * 见 `TokenRecord.ttlSeconds`。\n *\n * 🔴 必须在这里也留一份:`identities/<name>/credentials.json` 这**一个文件**有两个\n * 写入方——`TokenStore.forIdentity().writeMeta()` 写 TokenRecord 形状,`saveIdentity()`\n * 写 IdentityMeta 形状,后者最后写、且只写自己声明的字段。不在这里声明,\n * 登录流程末尾这一次 saveIdentity 就会把 ttlSeconds 抹掉,续期时只能去猜 TTL。\n */\n ttlSeconds?: number;\n}\n\nasync function assertEnvExists(env: string): Promise<void> {\n try {\n await fs.access(envFile(env));\n } catch {\n throw new CliError({\n code: CliErrorCode.IDENTITY_NOT_IN_ENV,\n message: `env '${env}' does not exist. Run: hcm env add ${env} --endpoint <url>`,\n });\n }\n}\n\nexport async function saveIdentity(name: string, meta: IdentityMeta): Promise<void> {\n await assertEnvExists(meta.env);\n await fs.mkdir(identityDir(name), { recursive: true });\n await fs.writeFile(identityMetaFile(name), JSON.stringify(meta, null, 2), { mode: 0o600 });\n}\n\nexport async function loadIdentity(name: string): Promise<IdentityMeta> {\n try {\n const raw = await fs.readFile(identityMetaFile(name), 'utf-8');\n return JSON.parse(raw) as IdentityMeta;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new CliError({\n code: CliErrorCode.IDENTITY_NOT_FOUND,\n message: `identity '${name}' not found. Run: hcm identity list`,\n });\n }\n throw e;\n }\n}\n\nexport async function listIdentities(env?: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(identitiesDir(), { withFileTypes: true });\n const result: string[] = [];\n for (const e of entries) {\n if (!e.isDirectory()) continue;\n try {\n const meta = await loadIdentity(e.name);\n if (!env || meta.env === env) result.push(e.name);\n } catch {\n /* skip broken */\n }\n }\n return result;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw e;\n }\n}\n\nexport async function deleteIdentity(name: string): Promise<void> {\n try {\n await fs.access(identityMetaFile(name));\n } catch {\n throw new CliError({\n code: CliErrorCode.IDENTITY_NOT_FOUND,\n message: `identity '${name}' not found`,\n });\n }\n await fs.rm(identityDir(name), { recursive: true, force: true });\n}\n","import * as fs from 'node:fs/promises';\nimport type { Dirent } from 'node:fs';\nimport * as path from 'node:path';\nimport yaml from 'js-yaml';\nimport {\n hcmConfigDir,\n profileDir,\n profileFile,\n credentialsFile,\n archiveDir,\n envFile,\n} from './paths';\nimport { saveEnv, deleteEnv } from './env';\nimport { saveIdentity, deleteIdentity } from './identity';\nimport { loadGlobalConfig, saveGlobalConfig } from './global-config';\n\ninterface LegacyProfile {\n endpoint: string;\n tenantId?: string;\n defaultAgent?: string;\n}\n\ninterface LegacyCredentials {\n scheme: 'password' | 'client_credentials' | 'pat';\n accessTokenRef: string;\n refreshTokenRef?: string;\n expiresAt: string;\n scopes: string[];\n username?: string;\n}\n\nexport interface MigrationSummary {\n migrated: string[];\n failed: string[];\n skipped: string[];\n}\n\nconst RESERVED = new Set(['envs', 'identities', '_archive']);\n\n/**\n * One-shot lazy migration from legacy `<hcm>/<profile>/profile.yml + credentials.json`\n * to canonical `<hcm>/envs/<name>/env.yml + identities/<name>/credentials.json`.\n *\n * Semantics:\n * - Idempotent: profiles already migrated (env.yml exists) are skipped.\n * - Per-profile atomicity: any failure inside migrateOne rolls back the partial env.\n * - Legacy files for failed profiles stay in place so the user can inspect / retry.\n * - On any migration success, the global config.yml is rewritten so activeEnv is\n * the canonical key (loadGlobalConfig already provides fallback read of\n * legacy activeProfile).\n * - Structured stderr log: `[hcm] event=migration_done count=N archive=PATH/`\n * and `[hcm] event=migration_failed profile=NAME reason=\"...\"`.\n */\nexport async function migrateLegacyProfiles(): Promise<MigrationSummary> {\n const summary: MigrationSummary = { migrated: [], failed: [], skipped: [] };\n const root = hcmConfigDir();\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(root, { withFileTypes: true });\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return summary;\n throw e;\n }\n\n for (const e of entries) {\n if (!e.isDirectory() || RESERVED.has(e.name)) continue;\n const legacyYml = profileFile(e.name);\n try {\n await fs.access(legacyYml);\n } catch {\n continue; // not a legacy profile dir\n }\n\n // already migrated?\n try {\n await fs.access(envFile(e.name));\n summary.skipped.push(e.name);\n continue;\n } catch {\n /* not yet migrated */\n }\n\n let result: { identityName: string } | null = null;\n try {\n result = await migrateOne(e.name);\n summary.migrated.push(e.name);\n } catch (err) {\n summary.failed.push(e.name);\n // Rollback: env AND identity (identity may exist if archive step failed)\n try {\n await deleteEnv(e.name);\n } catch {\n /* env was not yet created — nothing to roll back */\n }\n if (result === null) {\n // migrateOne threw before returning identityName; try to clean up by\n // computed name using the same inference migrateOne would have used.\n try {\n const creds = JSON.parse(\n await fs.readFile(credentialsFile(e.name), 'utf-8'),\n ) as { username?: string };\n const guessedId = creds?.username ? `${creds.username}@${e.name}` : `default@${e.name}`;\n await deleteIdentity(guessedId);\n } catch {\n /* identity not created or creds unreadable — fine */\n }\n } else {\n try {\n await deleteIdentity(result.identityName);\n } catch {\n /* not created */\n }\n }\n process.stderr.write(\n `[hcm] event=migration_failed profile=${e.name} reason=${JSON.stringify(String((err as Error).message))}\\n`,\n );\n }\n }\n\n // upgrade global config.yml: activeProfile → activeEnv (canonical key)\n if (summary.migrated.length > 0) {\n const g = await loadGlobalConfig(); // 自动兼容读 activeProfile fallback\n await saveGlobalConfig({ activeEnv: g.activeEnv, defaults: g.defaults });\n\n const archive = archiveDir();\n process.stderr.write(\n `[hcm] event=migration_done count=${summary.migrated.length} archive=${archive}/\\n`,\n );\n }\n\n return summary;\n}\n\nasync function migrateOne(name: string): Promise<{ identityName: string }> {\n const legacyProfile = yaml.load(\n await fs.readFile(profileFile(name), 'utf-8'),\n ) as LegacyProfile | null;\n if (!legacyProfile?.endpoint) {\n throw new Error('legacy profile.yml missing endpoint');\n }\n\n // Read credentials.json eagerly so a corrupt file fails BEFORE we touch env.yml.\n // (This way the rollback branch is exercised cleanly and the legacy files stay\n // in place for inspection — matches the \"partial fail\" test contract.)\n let creds: LegacyCredentials | null = null;\n try {\n creds = JSON.parse(\n await fs.readFile(credentialsFile(name), 'utf-8'),\n ) as LegacyCredentials;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new Error(`credentials.json unreadable: ${(e as Error).message}`);\n }\n // ENOENT → no credentials, that's fine\n }\n\n const identityName = creds?.username ? `${creds.username}@${name}` : `default@${name}`;\n\n // 1. write env.yml (with defaultIdentity pointer)\n await saveEnv(name, {\n endpoint: legacyProfile.endpoint,\n tenantId: legacyProfile.tenantId,\n defaultAgent: legacyProfile.defaultAgent,\n defaultIdentity: identityName,\n });\n\n // 2. write identity meta\n // - if creds existed: full meta carried over\n // - if creds missing: stub identity (scheme=password, empty scopes) — env's\n // defaultIdentity points here, user must `hcm login` to populate the token\n if (creds) {\n await saveIdentity(identityName, {\n env: name,\n scheme: creds.scheme,\n scopes: creds.scopes,\n expiresAt: creds.expiresAt,\n username: creds.username,\n accessTokenRef: creds.accessTokenRef,\n refreshTokenRef: creds.refreshTokenRef,\n });\n } else {\n // Stub: no credentials yet — set expiresAt in the past so auth-guard treats\n // this as \"expired, force re-login\" rather than \"valid password scheme\".\n // T13 login overwrites this with real meta when user runs `hcm login --env <name>`.\n await saveIdentity(identityName, {\n env: name,\n scheme: 'password',\n scopes: [],\n expiresAt: '1970-01-01T00:00:00Z',\n });\n }\n\n // 3. archive legacy files (only after env + identity are persisted)\n const archiveTarget = path.join(archiveDir(), name);\n await fs.mkdir(archiveTarget, { recursive: true });\n await fs.rename(profileFile(name), path.join(archiveTarget, 'profile.yml.bak'));\n try {\n await fs.rename(\n credentialsFile(name),\n path.join(archiveTarget, 'credentials.json.bak'),\n );\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e;\n }\n\n // try to remove the now-empty profile dir (may still hold non-migrated artifacts\n // like last-conversation.json or repl-history — keep dir in that case)\n try {\n await fs.rmdir(profileDir(name));\n } catch {\n /* not empty — leave dir for non-migrated artifacts */\n }\n\n return { identityName };\n}\n","import { loadEnv } from './env';\nimport { listIdentities } from './identity';\nimport { CliError, CliErrorCode } from '../errors';\n\n/**\n * Resolve active identity name.\n * Priority: --as flag > HCM_IDENTITY env var > env.defaultIdentity > first identity in env > throw.\n */\nexport async function resolveActiveIdentity(\n args: { asFlag?: string },\n env: NodeJS.ProcessEnv,\n activeEnv: string,\n): Promise<string> {\n if (args.asFlag) return args.asFlag;\n if (env.HCM_IDENTITY) return env.HCM_IDENTITY;\n const cfg = await loadEnv(activeEnv);\n if (cfg.defaultIdentity) return cfg.defaultIdentity;\n const [first] = await listIdentities(activeEnv);\n if (!first) {\n throw new CliError({\n code: CliErrorCode.IDENTITY_NOT_FOUND,\n message: `env '${activeEnv}' has no identity. Run: hcm login --env ${activeEnv}`,\n });\n }\n return first;\n}\n\n// Re-export resolveActiveEnv from global-config for symmetry\nexport { resolveActiveEnv } from './global-config';\n","import type { AxiosInstance } from 'axios';\nimport type { ModelQueryDsl, QueryResult } from './types';\n\ninterface HcmPageResponse<T> {\n content: T[];\n totalElements?: number;\n totalPages?: number;\n size?: number;\n page?: number;\n}\n\nexport async function query<T = Record<string, unknown>>(\n http: AxiosInstance,\n model: string,\n dsl: ModelQueryDsl,\n): Promise<QueryResult<T>> {\n const body: Record<string, unknown> = {};\n const filter = normalizeFilter(dsl.filter);\n if (filter !== undefined) body.filter = filter;\n const orders = normalizeOrders(dsl.sort);\n if (orders !== undefined) body.orders = orders;\n if (dsl.fields !== undefined) body.fields = dsl.fields;\n if (dsl.keyword !== undefined) body.keyword = dsl.keyword;\n\n const size = dsl.limit ?? 20;\n const page = dsl.offset ? Math.floor(dsl.offset / size) : 0;\n body.page = page;\n body.size = size;\n\n const resp = await http.post<HcmPageResponse<T>>(`/api/models/${model}/list/dsl`, body);\n return {\n rows: resp.data.content ?? [],\n total: resp.data.totalElements,\n };\n}\n\nconst OPERATOR_ALIASES: Record<string, string> = {\n $eq: 'eq',\n eq: 'eq',\n '=': 'eq',\n '==': 'eq',\n $ne: 'ne',\n ne: 'ne',\n '!=': 'ne',\n '<>': 'ne',\n $gt: 'gt',\n gt: 'gt',\n '>': 'gt',\n $gte: 'gte',\n gte: 'gte',\n '>=': 'gte',\n $lt: 'lt',\n lt: 'lt',\n '<': 'lt',\n $lte: 'lte',\n lte: 'lte',\n '<=': 'lte',\n $in: 'in',\n in: 'in',\n $nin: 'not_in',\n nin: 'not_in',\n $not_in: 'not_in',\n not_in: 'not_in',\n $like: 'like',\n like: 'like',\n $not_like: 'not_like',\n not_like: 'not_like',\n $contains: 'contains',\n contains: 'contains',\n $startswith: 'startswith',\n startswith: 'startswith',\n $endswith: 'endswith',\n endswith: 'endswith',\n $between: 'between',\n between: 'between',\n $is_null: 'is_null',\n is_null: 'is_null',\n $is_not_null: 'is_not_null',\n is_not_null: 'is_not_null',\n};\n\nfunction normalizeFilter(filter: Record<string, unknown> | undefined): unknown {\n if (filter === undefined) return undefined;\n if (!isPlainObject(filter)) return filter;\n if (isAstFilter(filter)) return filter;\n\n const leaves = Object.entries(filter).flatMap(([field, value]) => filterLeaves(field, value));\n if (leaves.length === 0) return undefined;\n return leaves.length === 1 ? leaves[0] : { and: leaves };\n}\n\nfunction isAstFilter(filter: Record<string, unknown>): boolean {\n return (\n 'and' in filter ||\n 'or' in filter ||\n 'not' in filter ||\n (typeof filter.field === 'string' && typeof filter.op === 'string')\n );\n}\n\nfunction filterLeaves(field: string, value: unknown): Array<Record<string, unknown>> {\n if (isPlainObject(value)) {\n const entries = Object.entries(value);\n if (entries.length > 0 && entries.every(([key]) => isFilterOperator(key))) {\n return entries.map(([key, operatorValue]) => {\n const op = OPERATOR_ALIASES[key];\n if (!op) {\n throw new Error(`Unsupported filter operator \"${key}\" for field \"${field}\"`);\n }\n return isUnaryOperator(op)\n ? { field, op }\n : { field, op, value: operatorValue };\n });\n }\n if (entries.some(([key]) => isFilterOperator(key))) {\n throw new Error(`Unsupported mixed filter value for field \"${field}\"`);\n }\n }\n return [{ field, op: Array.isArray(value) ? 'in' : 'eq', value }];\n}\n\nfunction isFilterOperator(key: string): boolean {\n return Object.prototype.hasOwnProperty.call(OPERATOR_ALIASES, key);\n}\n\nfunction isUnaryOperator(op: string): boolean {\n return op === 'is_null' || op === 'is_not_null';\n}\n\nfunction normalizeOrders(sort: Array<Record<string, unknown>> | undefined): unknown {\n if (sort === undefined) return undefined;\n return sort.map((item) => {\n const field = item.field;\n if (typeof field !== 'string' || field.trim() === '') {\n throw new Error('sort item requires non-empty field');\n }\n const rawDirection =\n typeof item.direction === 'string'\n ? item.direction\n : typeof item.dir === 'string'\n ? item.dir\n : typeof item.order === 'string'\n ? item.order\n : item.desc === true\n ? 'desc'\n : 'asc';\n const direction = rawDirection.toLowerCase();\n if (direction !== 'asc' && direction !== 'desc') {\n throw new Error(`sort direction must be asc or desc; got \"${rawDirection}\"`);\n }\n return { field, direction };\n });\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import type { AxiosInstance } from 'axios';\nimport type { ActionHttpMethod, ActionInput, ActionResult, ActionScope } from './types';\n\nfunction normalizeMethod(method?: ActionHttpMethod): ActionHttpMethod {\n return method ?? 'POST';\n}\n\nfunction normalizeScope(input: ActionInput): ActionScope {\n return input.scope ?? (input.id ? 'object' : 'class');\n}\n\nfunction queryParams(input: ActionInput): Record<string, unknown> {\n return {\n ...(input.params ?? {}),\n ...(input.scope === 'class' && input.id ? { id: input.id } : {}),\n ...(input.ids?.length ? { ids: input.ids.join(',') } : {}),\n };\n}\n\nfunction classBody(input: ActionInput): Record<string, unknown> {\n return {\n ...(input.params ?? {}),\n ...(input.id ? { id: input.id } : {}),\n ...(input.ids?.length ? { ids: input.ids } : {}),\n };\n}\n\nfunction objectBody(input: ActionInput): Record<string, unknown> {\n return input.params ?? {};\n}\n\nexport async function action<R = unknown>(\n http: AxiosInstance,\n model: string,\n actionName: string,\n input: ActionInput,\n): Promise<ActionResult<R>> {\n const method = normalizeMethod(input.method);\n const scope = normalizeScope(input);\n if (scope === 'object' && !input.id) {\n throw new Error(`object action ${model}.${actionName} requires id`);\n }\n\n const path = scope === 'object'\n ? `/api/models/${model}/${input.id}/action/${actionName}`\n : `/api/models/${model}/action/${actionName}`;\n\n const resp = await (async () => {\n if (method === 'GET') {\n return http.get(path, { params: queryParams(input) });\n }\n if (method === 'DELETE') {\n return http.delete(path, { params: queryParams(input) });\n }\n if (method === 'PUT') {\n return http.put(path, scope === 'object' ? objectBody(input) : classBody(input));\n }\n return http.post(path, scope === 'object' ? objectBody(input) : classBody(input));\n })();\n return resp.data as ActionResult<R>;\n}\n","import type { AxiosInstance } from 'axios';\n\nexport interface FieldMeta {\n name: string;\n type: string;\n required?: boolean;\n label?: string;\n description?: string;\n enum?: string[];\n relation?: { model: string; field?: string };\n}\n\nexport interface ActionMeta {\n name: string;\n label?: string;\n description?: string;\n params?: Array<{ name: string; type: string; required?: boolean; description?: string }>;\n scope?: string;\n httpMethod?: string;\n readOnly?: boolean;\n paramTypes?: string[];\n paramNames?: string[];\n returnType?: string;\n source?: string;\n}\n\nexport interface RelationMeta {\n key: string;\n via: string;\n label?: string;\n}\n\nexport interface ModelDescription {\n model: string;\n labels: Record<string, string>;\n fields: FieldMeta[];\n actions: ActionMeta[];\n relations: RelationMeta[];\n}\n\nexport interface DescribeOpts {\n verbose?: boolean;\n}\n\ninterface HcmFieldRaw {\n key?: string;\n name?: string;\n type?: string;\n isRequired?: boolean;\n description?: string;\n enum?: string[];\n relation?: { model: string; field?: string };\n}\n\ninterface HcmActionRaw {\n key?: string;\n name?: string;\n label?: string;\n description?: string;\n params?: Array<{ name: string; type: string; required?: boolean; description?: string }>;\n parameters?: Array<{ name: string; type: string; required?: boolean; description?: string }>;\n scope?: string;\n httpMethod?: string;\n readOnly?: boolean;\n paramTypes?: string[];\n paramNames?: string[];\n returnType?: string;\n source?: string;\n}\n\nfunction normalizeActionParams(\n action: HcmActionRaw,\n): Array<{ name: string; type: string; required?: boolean; description?: string }> | undefined {\n if (action.parameters) return action.parameters;\n if (action.params) return action.params;\n if (!action.paramNames?.length && !action.paramTypes?.length) return undefined;\n const size = Math.max(action.paramNames?.length ?? 0, action.paramTypes?.length ?? 0);\n return Array.from({ length: size }, (_, index) => ({\n name: action.paramNames?.[index] ?? `arg${index}`,\n type: action.paramTypes?.[index] ?? 'unknown',\n }));\n}\n\nexport async function describe(\n http: AxiosInstance,\n model: string,\n _opts: DescribeOpts = {},\n): Promise<ModelDescription> {\n const metaResp = await http.get(`/api/models/${model}/meta`);\n const meta = metaResp.data;\n const fields: FieldMeta[] = (meta.fields ?? []).map((f: HcmFieldRaw) => ({\n name: f.key ?? f.name ?? '',\n label: f.name,\n type: f.type ?? 'unknown',\n required: f.isRequired,\n description: f.description,\n enum: f.enum,\n relation: f.relation,\n }));\n const actions: ActionMeta[] = (meta.actions ?? []).map((a: HcmActionRaw) => ({\n name: a.key ?? a.name ?? '',\n label: a.label,\n description: a.description,\n params: normalizeActionParams(a),\n scope: a.scope,\n httpMethod: a.httpMethod,\n readOnly: a.readOnly,\n paramTypes: a.paramTypes,\n paramNames: a.paramNames,\n returnType: a.returnType,\n source: a.source,\n }));\n return {\n model: meta.name ?? model,\n labels: meta.labels ?? {},\n fields,\n actions,\n relations: meta.relations ?? [],\n };\n}\n","import type { AxiosInstance } from 'axios';\n\nexport interface CreateResult {\n id: string;\n [key: string]: unknown;\n}\n\n/** 创建一条 Model 记录,走后端 Model 层完整校验。\n * 注:返回体形态(尤其 id 字段名)见 spec §12.3,实现期对真后端核实。 */\nexport async function create(\n http: AxiosInstance,\n model: string,\n data: Record<string, unknown>,\n): Promise<CreateResult> {\n const resp = await http.post(`/api/models/${model}`, data);\n return resp.data as CreateResult;\n}\n","import type { AxiosInstance } from 'axios';\nimport type { CreateResult } from './create';\n\nexport async function update(\n http: AxiosInstance,\n model: string,\n id: string,\n data: Record<string, unknown>,\n): Promise<CreateResult> {\n const resp = await http.put(`/api/models/${model}/${id}`, data);\n return resp.data as CreateResult;\n}\n","import type { AxiosInstance } from 'axios';\n\nexport interface RemoveResult {\n id: string;\n status: number;\n data?: unknown;\n}\n\nexport async function remove(http: AxiosInstance, model: string, id: string): Promise<RemoveResult> {\n const resp = await http.delete(`/api/models/${model}/${encodeURIComponent(id)}`);\n if (resp.data && typeof resp.data === 'object') {\n return resp.data as RemoveResult;\n }\n return { id, status: resp.status };\n}\n","\n/** snake_case → camelCase(单个 key)。 */\nexport function snakeToCamel(key: string): string {\n return key.replace(/_([a-z0-9])/g, (_m, c: string) => c.toUpperCase());\n}\n\n/** 转换 record 的**顶层** key:snake_case → camelCase。\n * 嵌套对象/数组的值(jsonb / i18n)原样透传,不转内部键名(对齐 schema §4)。 */\nexport function camelizeKeys(obj: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n out[snakeToCamel(k)] = v;\n }\n return out;\n}\n","// 整字段值必须是 ${Model:key}(不嵌套、不拼接,对齐 schema §3)\nconst PLACEHOLDER_RE = /^\\$\\{([A-Za-z0-9_]+):([^}]+)\\}$/;\n\nexport class RefStore {\n private map = new Map<string, string>();\n\n private k(model: string, key: string): string {\n return `${model}:${key}`;\n }\n register(model: string, key: string, id: string): void {\n this.map.set(this.k(model, key), id);\n }\n get(model: string, key: string): string | undefined {\n return this.map.get(this.k(model, key));\n }\n snapshot(): Record<string, string> {\n return Object.fromEntries(this.map);\n }\n}\n\nexport interface ParsedPlaceholder {\n model: string;\n key: string;\n}\n\n/** 若 value 恰好是一个 ${Model:key} 占位符,返回其 model/key;否则 null。 */\nexport function parsePlaceholder(value: unknown): ParsedPlaceholder | null {\n if (typeof value !== 'string') return null;\n const m = PLACEHOLDER_RE.exec(value);\n if (!m) return null;\n const [, model, key] = m;\n if (model === undefined || key === undefined) return null;\n return { model, key };\n}\n\n/** 用 store 替换 record 顶层的 ${Model:key} 占位符。\n * 返回 { resolved, unresolved };unresolved 是 \"字段名=${Model:key}\" 列表,原值保留便于报错。 */\nexport function resolveRefs(\n record: Record<string, unknown>,\n store: RefStore,\n): { resolved: Record<string, unknown>; unresolved: string[] } {\n const resolved: Record<string, unknown> = {};\n const unresolved: string[] = [];\n for (const [field, value] of Object.entries(record)) {\n const ph = parsePlaceholder(value);\n if (!ph) {\n resolved[field] = value;\n continue;\n }\n const id = store.get(ph.model, ph.key);\n if (id === undefined) {\n unresolved.push(`${field}=\\${${ph.model}:${ph.key}}`);\n resolved[field] = value;\n } else {\n resolved[field] = id;\n }\n }\n return { resolved, unresolved };\n}\n","import yaml, { JSON_SCHEMA } from 'js-yaml';\nimport { CliError, CliErrorCode } from '../errors';\nimport type { Fixture } from './types';\n\nconst REQUIRED = ['model', 'key_field', 'count', 'items'] as const;\n\n/** 解析 + 校验单个 data fixture(对齐 spec §10 校验项 1–4)。 */\nexport function parseFixture(yamlText: string, sourcePath?: string): Fixture {\n const where = sourcePath ? ` (${sourcePath})` : '';\n const fail = (message: string): never => {\n throw new CliError({ code: CliErrorCode.INVALID_ARGUMENT, message: `import: ${message}${where}` });\n };\n\n // 注入数据视为不可信:用 JSON_SCHEMA(只认 JSON 可表达的类型,拒绝 !!js/!!python 等自定义 tag),\n // 比 js-yaml v4 默认 DEFAULT_SCHEMA 更严;也避免无引号日期被解析成 Date 对象。\n let parsed: unknown;\n try {\n parsed = yaml.load(yamlText, { schema: JSON_SCHEMA });\n } catch (e) {\n return fail(`YAML 解析失败: ${(e as Error).message}`);\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return fail('fixture 为空或不是 mapping');\n }\n const doc = parsed as Record<string, unknown>;\n for (const f of REQUIRED) {\n if (doc[f] === undefined) return fail(`fixture 缺必需字段 \"${f}\"`);\n }\n if (!Array.isArray(doc.items)) return fail('\"items\" 必须是 list');\n if (!Number.isInteger(doc.count)) return fail('\"count\" 必须是整数');\n if (doc.items.length !== doc.count) {\n return fail(`count mismatch: count=${doc.count} 但 items 有 ${doc.items.length}`);\n }\n\n const keyField = doc.key_field as string;\n const seen = new Set<string>();\n for (const item of doc.items as Array<Record<string, unknown>>) {\n const k = item[keyField];\n if (k === undefined || k === null || k === '') return fail(`item 缺 key_field \"${keyField}\"`);\n if (seen.has(String(k))) return fail(`duplicate key \"${k}\" in ${String(doc.model)}`);\n seen.add(String(k));\n }\n return { ...(doc as unknown as Fixture), sourcePath };\n}\n","import { camelizeKeys } from './fields';\nimport { RefStore, resolveRefs } from './placeholder';\nimport type { Fixture, ImportOptions, ImportResult, RowResult } from './types';\n\n/** runner 只依赖 client 的这三个方法,便于单测 stub。 */\nexport interface ImportClient {\n findIdByKey(model: string, keyField: string, keyValue: string): Promise<string | null>;\n create(model: string, data: Record<string, unknown>): Promise<{ id: string }>;\n update(model: string, id: string, data: Record<string, unknown>): Promise<unknown>;\n}\n\nexport async function runImport(\n client: ImportClient,\n fixtures: Fixture[],\n opts: ImportOptions = {},\n): Promise<ImportResult> {\n const store = new RefStore();\n const rows: RowResult[] = [];\n const tally = { created: 0, updated: 0, skipped: 0, failed: 0 };\n const result = (): ImportResult => ({ ...tally, rows, refs: store.snapshot() });\n\n for (const fx of fixtures) {\n for (const item of fx.items) {\n const key = String(item[fx.key_field]);\n const { resolved, unresolved } = resolveRefs(item, store);\n\n if (unresolved.length > 0) {\n rows.push({ model: fx.model, key, status: 'failed', error: `unresolved placeholder(s): ${unresolved.join(', ')}` });\n tally.failed++;\n if (opts.onError === 'stop') return result();\n continue;\n }\n\n if (opts.dryRun) {\n // 纯静态:不写后端;注册假 id 让后续跨文件占位符仍能解析\n store.register(fx.model, key, `dryrun-${fx.model}-${key}`);\n rows.push({ model: fx.model, key, status: 'skipped' });\n tally.skipped++;\n continue;\n }\n\n const payload = camelizeKeys(resolved);\n try {\n const existingId = await client.findIdByKey(fx.model, fx.key_field, key);\n if (existingId && !opts.updateOnly) {\n store.register(fx.model, key, existingId);\n rows.push({ model: fx.model, key, status: 'skipped', id: existingId });\n tally.skipped++;\n } else if (existingId && opts.updateOnly) {\n await client.update(fx.model, existingId, payload);\n store.register(fx.model, key, existingId);\n rows.push({ model: fx.model, key, status: 'updated', id: existingId });\n tally.updated++;\n } else {\n const created = await client.create(fx.model, payload);\n store.register(fx.model, key, created.id);\n rows.push({ model: fx.model, key, status: 'created', id: created.id });\n tally.created++;\n }\n } catch (e: unknown) {\n const err = e as { response?: { headers?: Record<string, string>; data?: { message?: string } }; message?: string };\n rows.push({\n model: fx.model, key, status: 'failed',\n error: err.response?.data?.message ?? err.message ?? 'write failed',\n traceId: err.response?.headers?.['x-trace-id'],\n });\n tally.failed++;\n if (opts.onError === 'stop') return result();\n }\n }\n }\n return result();\n}\n","import type { AxiosInstance } from 'axios';\nimport { createHttpClient } from './http';\nimport { TokenStore } from './auth/token-store';\nimport { refreshToken, needsRefresh } from './auth/refresh';\nimport { loadProfile, loadGlobalConfig, type ProfileConfig } from './config';\nimport { query as modelQuery } from './models/query';\nimport { action as modelAction } from './models/action';\nimport { describe as describeModel } from './meta/describe';\nimport type { ModelQueryDsl, QueryResult, ActionInput, ActionResult } from './models/types';\nimport type { ModelDescription, DescribeOpts } from './meta/describe';\nimport {\n create as modelCreate,\n update as modelUpdate,\n remove as modelRemove,\n type CreateResult,\n type RemoveResult,\n} from './models';\nimport { runImport, type ImportClient } from './import';\nimport type { Fixture, ImportOptions, ImportResult } from './import';\n\nexport interface HcmClientOpts {\n endpoint: string;\n tenantId: string;\n accessToken?: string; // 显式构造时传\n scheme?: 'client_credentials' | 'pat';\n profile?: string;\n timeoutMs?: number;\n}\n\n/**\n * Structural type representing the resolved auth context that\n * `HcmClient.fromAuthContext` consumes. Kept as a structural interface\n * (rather than importing `AuthContext` from `@hcmai/cli`) to avoid a circular\n * package dependency — the CLI's `AuthContext` is assignable to this shape.\n */\nexport interface ClientAuthContext {\n env: string;\n endpoint: string;\n tenantId: string;\n identity: string;\n}\n\nexport class HcmClient {\n readonly profile: string;\n readonly endpoint: string;\n readonly tenantId: string;\n private http: AxiosInstance;\n private tokenStore: TokenStore;\n private explicitToken?: string;\n\n constructor(opts: HcmClientOpts) {\n this.profile = opts.profile ?? 'default';\n this.endpoint = opts.endpoint;\n this.tenantId = opts.tenantId;\n this.tokenStore = new TokenStore(this.profile);\n this.explicitToken = opts.accessToken;\n this.http = createHttpClient({\n endpoint: opts.endpoint,\n timeoutMs: opts.timeoutMs,\n getAccessToken: () => this.getAccessToken(),\n onRefresh: () => this.handleRefresh(),\n });\n }\n\n /**\n * Build a client from a resolved env + identity context (T15).\n *\n * Uses `TokenStore.forIdentity(ctx.identity)` so the token is read from\n * the identity-scoped store (written by `hcm login --env <env> --name <id>`),\n * not the legacy profile-scoped credentials.json.\n *\n * The `profile` field is kept = `ctx.env` for downstream callers that still\n * read `client.profile` (e.g. workspace context dump). Phase 2 will rename\n * the field to `env`.\n *\n * Refresh path: fromAuthContext binds `this.identity` so handleRefresh\n * routes refresh through TokenStore.forIdentity — works for\n * client_credentials sessions (1h TTL) post-T8 migration.\n */\n /**\n * Identity name for refresh path (set by fromAuthContext). When set,\n * handleRefresh routes through TokenStore.forIdentity rather than the\n * profile-scoped legacy store — critical for client_credentials sessions\n * (~1h token TTL) on post-T8-migration installs.\n */\n private identity?: string;\n\n static fromAuthContext(ctx: ClientAuthContext): HcmClient {\n const c = new HcmClient({\n endpoint: ctx.endpoint,\n tenantId: ctx.tenantId,\n profile: ctx.env, // alias env→profile for downstream readers\n });\n (c as unknown as { tokenStore: TokenStore }).tokenStore = TokenStore.forIdentity(ctx.identity);\n (c as unknown as { identity: string }).identity = ctx.identity;\n return c;\n }\n\n /**\n * @deprecated Use `HcmClient.fromAuthContext(ctx)` together with the CLI\n * helper `getActiveAuthContextFromCli(args)`. WILL THROW on post-T8\n * installs where `~/.config/hcm/<profile>/profile.yml` was migrated to\n * `envs/<name>/env.yml` (loadProfile reads the legacy path only).\n * Kept only for dead-code paths (mini.ts) and pre-migration users.\n */\n static async fromConfig(args: { profile?: string }): Promise<HcmClient> {\n const global = await loadGlobalConfig();\n const profile = args.profile ?? process.env.HCM_PROFILE ?? global.activeEnv;\n const cfg: ProfileConfig = await loadProfile(profile);\n return new HcmClient({\n endpoint: cfg.endpoint,\n tenantId: cfg.tenantId,\n profile,\n });\n }\n\n query<T = Record<string, unknown>>(model: string, dsl: ModelQueryDsl): Promise<QueryResult<T>> {\n return modelQuery<T>(this.http, model, dsl);\n }\n\n action<R = unknown>(model: string, name: string, input: ActionInput): Promise<ActionResult<R>> {\n return modelAction<R>(this.http, model, name, input);\n }\n\n describe(model: string, opts?: DescribeOpts): Promise<ModelDescription> {\n return describeModel(this.http, model, opts);\n }\n\n create(model: string, data: Record<string, unknown>): Promise<CreateResult> {\n return modelCreate(this.http, model, data);\n }\n\n update(model: string, id: string, data: Record<string, unknown>): Promise<CreateResult> {\n return modelUpdate(this.http, model, id, data);\n }\n\n remove(model: string, id: string): Promise<RemoveResult> {\n return modelRemove(this.http, model, id);\n }\n\n /** 按业务键查已存在记录的 id(幂等用);命中返回 id,否则 null。 */\n async findIdByKey(model: string, keyField: string, keyValue: string): Promise<string | null> {\n const res = await this.query(model, { filter: { [keyField]: keyValue }, limit: 1 });\n const row = res.rows[0] as { id?: string } | undefined;\n return row?.id ?? null;\n }\n\n importPackage(fixtures: Fixture[], opts?: ImportOptions): Promise<ImportResult> {\n return runImport(this as ImportClient, fixtures, opts);\n }\n\n raw(): AxiosInstance {\n return this.http;\n }\n\n async close(): Promise<void> {\n /* nothing for REST; chat module will override */\n }\n\n async getAccessToken(): Promise<string | null> {\n if (this.explicitToken) return this.explicitToken;\n return this.tokenStore.getAccessToken();\n }\n\n async handleRefresh(): Promise<void> {\n if (this.explicitToken) {\n // 显式 token 模式不能 refresh\n throw new Error('explicit token expired; pass a new accessToken');\n }\n const meta = await this.tokenStore.readMeta();\n if (!meta) throw new Error('no credentials');\n if (meta.scheme === 'pat') throw new Error('PAT cannot refresh; re-run login');\n if (needsRefresh(meta, 5)) {\n // Route refresh through identity-scoped store when bound via\n // fromAuthContext (T15 follow-up #3 — fixes client_credentials sessions\n // breaking ~1h in post-T8 installs).\n const refreshArgs = this.identity\n ? { endpoint: this.endpoint, identity: this.identity, httpClient: this.http }\n : { endpoint: this.endpoint, profile: this.profile, httpClient: this.http };\n await refreshToken(refreshArgs);\n }\n }\n}\n","import * as fs from 'node:fs';\nimport * as fsp from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { AxiosInstance } from 'axios';\n\n/**\n * 会话附件元数据——与后端 `ConversationWebSocketHandler.parseAttachments` 形态一致\n * (message.create payload.attachments 元素;#1849 后随 USER_MESSAGE 入 journal 单源)。\n */\nexport interface AttachmentMeta {\n documentId: string;\n fileName: string;\n mimeType: string;\n fileSize: number;\n}\n\nconst MIME_BY_EXT: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n '.doc': 'application/msword',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n '.csv': 'text/csv',\n '.txt': 'text/plain',\n '.md': 'text/markdown',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n};\n\nexport function guessMimeType(filePath: string): string {\n return MIME_BY_EXT[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n}\n\n/**\n * 聊天附件的存储分类 —— `hcm chat` 走这一类,与 web ChatInput 同端点同分类。\n *\n * <p>⚠️ 它**不是**通用默认值:`storageCategory` 是服务端的一份**数据**(可配置的\n * 分类表),不是枚举常量。分类不存在时服务端返回 404 `category not found`。\n */\nexport const CHAT_ATTACHMENT_CATEGORY = 'ai-conversation';\n\n/**\n * 上传本地文件到 HCM 文档存储,返回可直接携带的附件元数据。\n * 类型限制跟随服务端校验。\n *\n * @param storageCategory 存储分类。**必须是目标环境里真实存在的分类**——\n * 它是服务端数据而非枚举,不同环境的分类表可以不同。\n * 聊天附件传 {@link CHAT_ATTACHMENT_CATEGORY};报表模板一类传 `flex-report`。\n *\n * <p>🔴 此前这里硬编码 `'ai-conversation'`,于是 `hcm upload <任意文件>` 在\n * 没有该分类的环境上**一律失败**,而错误被包成\n * `BUSINESS_RULE_VIOLATION: Request failed with status code 404`——看不出真因是\n * 「分类不存在」。2026-08-26 按交付技能跑弹性报表端到端时,Step 4「上传模板」\n * 卡在这里;模板是报表的必需件,这条不通整条链就断了。\n */\nexport async function uploadDocument(\n http: AxiosInstance,\n filePath: string,\n storageCategory: string = CHAT_ATTACHMENT_CATEGORY,\n): Promise<AttachmentMeta> {\n const abs = expandHome(filePath);\n const buf = await fsp.readFile(abs);\n const fileName = path.basename(abs);\n const form = new FormData();\n form.append('file', new Blob([buf], { type: guessMimeType(abs) }), fileName);\n form.append('storageCategory', storageCategory);\n form.append('allowEmptyFormId', 'true');\n const resp = await http.post('/api/documents/upload', form);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const meta: any = resp.data?.data ?? resp.data;\n return {\n documentId: String(meta.documentId ?? meta.id),\n fileName: String(meta.fileName ?? fileName),\n mimeType: String(meta.contentType ?? meta.mimeType ?? guessMimeType(abs)),\n fileSize: Number(meta.fileSize ?? meta.size ?? buf.length),\n };\n}\n\n/** 默认产物落盘目录(对标 Claude Code 工具产物本地直接访问)。 */\nexport function defaultDownloadDir(): string {\n return path.join(os.homedir(), 'Downloads', 'hcm');\n}\n\n/**\n * 把后端相对下载链接(`/api/documents/download/{id}?...sig=`)拼成可点的绝对 URL,\n * 供终端渲染——签名 URL 自洽免 Bearer,点开即由浏览器打开(ADR-140)。\n * apiBase 为 CLI 连接的后端地址(`HcmClient.endpoint`);无 apiBase 或已是绝对 URL 时原样返回。\n */\nexport function absoluteDownloadUrl(apiBase: string | undefined, url: string): string {\n if (!apiBase || !url.startsWith('/')) return url;\n return apiBase.replace(/\\/+$/, '') + url;\n}\n\n/**\n * 认证下载文档到本地(`/api/documents/download` 需 Bearer,浏览器裸 GET 恒 401 的同款教训)。\n * destPath 为目录或省略时落 defaultDownloadDir,重名自动加序号。返回写入的绝对路径。\n */\nexport async function downloadDocument(\n http: AxiosInstance,\n ref: { documentId?: string; downloadUrl?: string; fileName?: string },\n destPath?: string,\n): Promise<string> {\n const url = ref.downloadUrl ?? `/api/documents/download/${ref.documentId}`;\n const resp = await http.get(url, { responseType: 'arraybuffer' });\n const headerName = parseFilenameFromDisposition(resp.headers?.['content-disposition']);\n // 🔴 路径穿越收口:fileName 来自服务端(content-disposition)/ 工具 result(LLM 可控),\n // safeBasename 剥掉所有目录段——杜绝 \"../../../.ssh/authorized_keys\" 类静默任意文件写\n // (auto-download 默认开,恶意工具 result 否则可写任意路径)。\n const rawName = ref.fileName ?? headerName ?? `${ref.documentId ?? 'download'}.bin`;\n\n // 目标目录与文件名解析:destPath 为目录/省略 → 落该目录 + 服务端 fileName;\n // destPath 指向文件 → 落其父目录 + destPath 的 basename(用户显式命名)。\n let dir: string;\n let nameSource: string;\n const expanded = destPath ? expandHome(destPath) : undefined;\n if (expanded && (expanded.endsWith(path.sep) || isExistingDir(expanded))) {\n dir = expanded;\n nameSource = rawName;\n } else if (expanded) {\n dir = path.dirname(expanded);\n nameSource = path.basename(expanded);\n } else {\n dir = defaultDownloadDir();\n nameSource = rawName;\n }\n const finalName = safeBasename(nameSource, ref.documentId);\n let target = path.join(dir, finalName);\n // 纵深防御:归属断言——target 必须在 dir 内(safeBasename 后理应恒真,留作回潮保险)。\n const resolvedDir = path.resolve(dir);\n if (path.resolve(target) !== path.join(resolvedDir, finalName)) {\n throw new Error(`下载路径越界:${target} 不在 ${resolvedDir} 内`);\n }\n await fsp.mkdir(dir, { recursive: true });\n target = dedupePath(target);\n await fsp.writeFile(target, Buffer.from(resp.data as ArrayBuffer));\n return target;\n}\n\nfunction expandHome(p: string): string {\n return p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : path.resolve(p);\n}\n\n/**\n * 文件名净化:剥所有目录段(path.basename 处理 / 与 \\\\)+ 拒 .. / 空 / 纯点。\n * 退化时回落 documentId 或 'download',绝不返回可逃出目标目录的串。\n */\nfunction safeBasename(rawName: string, documentId?: string): string {\n // 先归一反斜杠(防 Windows 风格 ..\\\\ 在 posix path.basename 下漏网)。\n const base = path.basename(rawName.replace(/\\\\/g, '/'));\n if (!base || base === '.' || base === '..' || base.includes('/')) {\n return documentId ? `${documentId}.bin` : 'download.bin';\n }\n return base;\n}\n\nfunction isExistingDir(p: string): boolean {\n try {\n return fs.statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** 重名文件加序号:report.xlsx → report (1).xlsx。 */\nfunction dedupePath(target: string): string {\n if (!fs.existsSync(target)) return target;\n const dir = path.dirname(target);\n const ext = path.extname(target);\n const base = path.basename(target, ext);\n for (let i = 1; ; i++) {\n const candidate = path.join(dir, `${base} (${i})${ext}`);\n if (!fs.existsSync(candidate)) return candidate;\n }\n}\n\nfunction parseFilenameFromDisposition(disposition?: string): string | undefined {\n if (!disposition) return undefined;\n const star = /filename\\*=UTF-8''([^;]+)/i.exec(disposition);\n if (star?.[1]) {\n try {\n return decodeURIComponent(star[1]);\n } catch {\n /* fallthrough */\n }\n }\n const plain = /filename=\"?([^\";]+)\"?/i.exec(disposition);\n return plain?.[1];\n}\n","export function toJson(value: unknown): string {\n return JSON.stringify(value, null, 2);\n}\n","import yaml from 'js-yaml';\n\nexport function toYaml(value: unknown): string {\n return yaml.dump(value, { indent: 2, lineWidth: -1 });\n}\n","import Table from 'cli-table3';\nimport { displayWidth, truncateDisplay } from '../chat/display-width';\n\nconst MAX_COL_WIDTH = 40;\n\n/**\n * 盒式文本表格。列宽**按内容自适应**、以 {@link MAX_COL_WIDTH} 为上限——短列(SEQ/ROUND\n * 等)不再被强制撑到 40 列(旧 `colWidths.map(()=>40)` 的 BUG:7 列表 ≈ 290 列宽、糊成一片)。\n * 宽度与截断均按**终端显示宽度**(CJK=2 列)计,避免中文单元格溢出错位。\n */\nexport function toTable(rows: Record<string, unknown>[]): string {\n if (rows.length === 0) return '(no results)';\n const cols = Object.keys(rows[0]!);\n const heads = cols.map((c) => c.toUpperCase());\n const cells = rows.map((r) => cols.map((c) => formatCell(r[c])));\n\n // 每列内容宽 = min(上限, max(表头, 各单元格显示宽))。\n const contentWidths = cols.map((_, i) =>\n Math.min(\n MAX_COL_WIDTH,\n Math.max(displayWidth(heads[i]!), ...cells.map((row) => displayWidth(row[i]!))),\n ),\n );\n\n const table = new Table({\n head: heads,\n style: { head: ['cyan'] },\n // +2:cli-table3 的 colWidths 含左右各 1 列 padding。\n colWidths: contentWidths.map((w) => w + 2),\n wordWrap: true,\n });\n for (const row of cells) table.push(row);\n return table.toString();\n}\n\nfunction formatCell(v: unknown): string {\n if (v === null || v === undefined) return '';\n const s = typeof v === 'object' ? JSON.stringify(v) : String(v);\n return truncateDisplay(s, MAX_COL_WIDTH);\n}\n","/**\n * 终端显示宽度(east-asian-width 近似,零依赖):CJK 表意/全角标点/假名/谚文按 2 列,\n * 其余按 1 列。argsPreview 等终端单行截断必须按显示宽度——按字符数截 120 的中文实际\n * 占 240 列必折行(2026-06-11 实测工具行折行根因)。\n */\nfunction charDisplayWidth(cp: number): number {\n return (cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo\n (cp >= 0x2e80 && cp <= 0xa4cf) || // CJK 部首/注音/汉字/假名\n (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul 音节\n (cp >= 0xf900 && cp <= 0xfaff) || // CJK 兼容表意\n (cp >= 0xfe30 && cp <= 0xfe4f) || // CJK 兼容形式\n (cp >= 0xff00 && cp <= 0xff60) || // 全角 ASCII/标点\n (cp >= 0xffe0 && cp <= 0xffe6) ||\n (cp >= 0x20000 && cp <= 0x3fffd) // CJK 扩展 B+\n ? 2\n : 1;\n}\n\n/** 字符串的终端显示宽度(列数)。 */\nexport function displayWidth(s: string): number {\n let w = 0;\n for (const ch of s) w += charDisplayWidth(ch.codePointAt(0) as number);\n return w;\n}\n\n/** 按显示宽度截断,超宽时截到 maxWidth-1 列并补「…」;未超宽原样返回。 */\nexport function truncateDisplay(s: string, maxWidth: number): string {\n if (displayWidth(s) <= maxWidth) return s;\n let w = 0;\n let out = '';\n for (const ch of s) {\n const cw = charDisplayWidth(ch.codePointAt(0) as number);\n if (w + cw > maxWidth - 1) break;\n w += cw;\n out += ch;\n }\n return out + '…';\n}\n","import { toJson } from './json';\nimport { toYaml } from './yaml';\nimport { toTable } from './table';\n\nexport type OutputFormat = 'table' | 'json' | 'yaml';\n\nexport interface FormatOptions {\n format: OutputFormat;\n}\n\nexport function formatRows(rows: Record<string, unknown>[], opts: FormatOptions): string {\n switch (opts.format) {\n case 'json':\n return toJson(rows);\n case 'yaml':\n return toYaml(rows);\n case 'table':\n return toTable(rows);\n }\n}\n\nexport function formatObject(obj: unknown, opts: FormatOptions): string {\n switch (opts.format) {\n case 'json':\n return toJson(obj);\n case 'yaml':\n return toYaml(obj);\n case 'table':\n // 单对象 fallback 到 json\n return toJson(obj);\n }\n}\n\nexport { toJson, toYaml, toTable };\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, type TokenRecord } from './token-store';\nimport { type LoginOutcome } from './principal';\n\nexport interface ClientCredentialsInput {\n endpoint: string;\n tenantId: string;\n clientId: string;\n clientSecret: string;\n profile: string;\n httpClient: AxiosInstance;\n}\n\nexport async function loginClientCredentials(\n input: ClientCredentialsInput,\n): Promise<LoginOutcome> {\n const resp = await input.httpClient.post(\n `${input.endpoint}/api/oauth/token`,\n {\n grant_type: 'client_credentials',\n client_id: input.clientId,\n client_secret: input.clientSecret,\n },\n { headers: { 'Content-Type': 'application/json' } },\n );\n const { access_token, refresh_token, expires_in, scope } = resp.data;\n if (!access_token) {\n throw new Error('OAuth response missing access_token');\n }\n\n const ttlSeconds = expires_in ?? 3600;\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();\n const scopes = (scope ?? '').split(/\\s+/).filter(Boolean);\n\n const store = new TokenStore(input.profile);\n const accessTokenRef = await store.setAccessToken(access_token);\n const refreshTokenRef = refresh_token ? await store.setRefreshToken(refresh_token) : undefined;\n\n const record: TokenRecord = {\n scheme: 'client_credentials',\n accessTokenRef,\n refreshTokenRef,\n expiresAt,\n scopes,\n ttlSeconds,\n };\n await store.writeMeta(record);\n // client_credentials 是机器身份,服务端不回 user —— principal 恒为 undefined,\n // 这与「口令登录拿不到身份」是两件事,别在回显上合并。\n return { record };\n}\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, type TokenRecord } from './token-store';\nimport { toPrincipal, type LoginOutcome } from './principal';\n\nexport interface PatLoginInput {\n endpoint: string;\n tenantId: string;\n pat: string;\n profile: string;\n httpClient: AxiosInstance;\n}\n\nexport async function loginPat(input: PatLoginInput): Promise<LoginOutcome> {\n // 验证 PAT 有效 + 拿用户/scope 信息\n const resp = await input.httpClient.get(`${input.endpoint}/api/auth/info`, {\n headers: { Authorization: `Bearer ${input.pat}` },\n });\n const scopes: string[] = resp.data?.scopes ?? [];\n // `/api/auth/info` 回的是 UserInfoResponse(userId/username/tenantId/roles) ——\n // PAT 到底挂在谁头上,只有这里说得出来。\n\n const store = new TokenStore(input.profile);\n const accessTokenRef = await store.setAccessToken(input.pat);\n\n // PAT 没有 refresh,过期时间设为 1 年(PAT 由后端管失效)\n const expiresAt = new Date(Date.now() + 365 * 24 * 3600 * 1000).toISOString();\n\n const record: TokenRecord = {\n scheme: 'pat',\n accessTokenRef,\n expiresAt,\n scopes,\n };\n await store.writeMeta(record);\n return { record, principal: toPrincipal(resp.data) };\n}\n","import type { AxiosInstance } from 'axios';\nimport { TokenStore, type TokenRecord } from './token-store';\nimport { toPrincipal, type LoginOutcome } from './principal';\nimport { DEFAULT_SESSION_TTL_SECONDS } from './password';\n\nexport interface PairingLoginInput {\n endpoint: string;\n /** 页面上复制来的配对码。大小写与连字符由服务端归一化,这里原样传。 */\n code: string;\n profile: string;\n httpClient: AxiosInstance;\n}\n\n/**\n * 配对码登录(ADR-308 第四轨)。\n *\n * 解的是「没有任何一方知道后端地址与 tenantId」:CLI 只有磁盘上人手填的值,用户知道的\n * 是浏览器地址栏那一串,后端全树无 frontend URL 配置。唯一同时知道这两件事的是那个\n * 已登录的页面——所以地址由命令行位置参数带进来(人从页面复制整行),租户由服务端\n * 在响应里给出,**不再有 `'1'` 这个猜测**。\n *\n * 🔴 `scheme` 记 `'pairing'` 而不是 `'password'`:如实记录来路(审计与回显需要)。\n * 但它换来的是 `TokenManager.createToken` 铸的**普通会话 token**,续期/吊销语义与口令\n * 登录完全一致——这一点由 `isServerSlidingSession` 谓词保证,别在别处写\n * `=== 'password'` 的比较。\n */\nexport async function loginPairing(input: PairingLoginInput): Promise<LoginOutcome> {\n const resp = await input.httpClient.post(\n `${input.endpoint}/api/auth/cli/exchange`,\n { code: input.code },\n { headers: { 'Content-Type': 'application/json' } },\n );\n // 响应刻意与 /api/auth/login 同形(LoginResponse),所以这里能与口令轨共用解析。\n const { token, expiresIn, user } = resp.data;\n if (!token) {\n throw new Error('Pairing exchange response missing token');\n }\n\n const ttlSeconds = expiresIn ?? DEFAULT_SESSION_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();\n\n const store = new TokenStore(input.profile);\n const accessTokenRef = await store.setAccessToken(token);\n\n const record: TokenRecord = {\n scheme: 'pairing',\n accessTokenRef,\n // 与口令会话同样没有 refresh token:续期语义是「同一个 token 被服务端重设 TTL」。\n refreshTokenRef: undefined,\n expiresAt,\n scopes: ['*'],\n ttlSeconds,\n };\n await store.writeMeta(record);\n return { record, principal: toPrincipal(user) };\n}\n\n/**\n * 从用户粘贴的地址里取出 origin。\n *\n * 人复制的是**浏览器地址栏那一串**,带路径、带查询参数都正常——那正是他唯一确定知道的\n * 东西。要求他先手工削成 origin,等于把「知道后端地址」这个门槛又搬了回来。\n */\nexport function toOrigin(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `https://${raw}`;\n const url = new URL(withScheme);\n return url.origin;\n}\n\n/**\n * 由 origin 推一个 env 名:`https://hcm.custa.com` → `custa`。\n *\n * 取的是主域左边那一段而不是整个 hostname:`hcm.custa.com` 与 `hcm-test.custa.com`\n * 会推出不同的名字,而 `hcm.custa.com` 与 `www.custa.com` 也不会撞。\n * 推不出来时落到 `default`,由调用方处理重名。\n */\nexport function inferEnvName(origin: string): string {\n const host = new URL(origin).hostname;\n if (/^\\d+(\\.\\d+)*$/.test(host) || host === 'localhost') return 'local';\n const parts = host.split('.').filter(Boolean);\n if (parts.length === 0) return 'default';\n // hcm.custa.com → custa(倒数第二段);custa.com → custa;单段 → 该段\n const candidate = parts.length >= 2 ? parts[parts.length - 2] : parts[0];\n return candidate || 'default';\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 首登强制改密(fresh tenant 的必经一步)。\n *\n * <p>后端契约(AuthController):\n * <ul>\n * <li>用初始口令 `POST /api/auth/login` → **403**,body 是\n * `{error:{code:\"AUTH_PASSWORD_CHANGE_REQUIRED\", details:{changePasswordToken, expiresInSeconds}}}`\n * —— 那个临时 token **只活 300 秒**;</li>\n * <li>拿它作 Bearer 调 `POST /api/auth/change-password`,body `{oldPassword,newPassword,confirmPassword}`;</li>\n * <li>改完用新口令重新 login 才拿到正式 token。</li>\n * </ul>\n *\n * <p>🔴 这是 `hcm tenant bootstrap` 之后的**硬阻塞**:不走这一步,新建的租户登不进去。\n * 所以 `hcm login` 自动识别并接管,而不是让顾问自己去拼两条 curl。\n */\n\nexport const PASSWORD_CHANGE_REQUIRED = 'AUTH_PASSWORD_CHANGE_REQUIRED';\n\nexport interface PasswordChangeChallenge {\n changePasswordToken: string;\n expiresInSeconds?: number;\n}\n\n/**\n * 从一个失败的 login 响应里识别「强制改密」挑战。\n *\n * 不是该挑战(网络错、口令错、账号锁定…)一律返回 null,由调用方按原错误处理——\n * 判据只认后端的错误码,不靠 HTTP 状态猜(423 账号锁定同样是 4xx,不能混)。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function detectPasswordChangeChallenge(err: any): PasswordChangeChallenge | null {\n const body = err?.response?.data;\n const error = body?.error ?? body;\n if (error?.code !== PASSWORD_CHANGE_REQUIRED) return null;\n const token = error?.details?.changePasswordToken;\n if (!token) return null;\n return {\n changePasswordToken: String(token),\n expiresInSeconds: Number(error?.details?.expiresInSeconds) || undefined,\n };\n}\n\nexport interface ChangePasswordInput {\n endpoint: string;\n /** 登录挑战里拿到的临时 token(300 秒有效) */\n changePasswordToken: string;\n oldPassword: string;\n newPassword: string;\n httpClient: AxiosInstance;\n}\n\nexport async function changePassword(input: ChangePasswordInput): Promise<void> {\n await input.httpClient.post(\n `${input.endpoint}/api/auth/change-password`,\n {\n oldPassword: input.oldPassword,\n newPassword: input.newPassword,\n confirmPassword: input.newPassword,\n },\n {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${input.changePasswordToken}`,\n },\n },\n );\n}\n","import type { AxiosInstance } from 'axios';\n\nexport interface AdminResetPasswordRequest {\n userId?: string;\n username?: string;\n newPassword: string;\n confirmPassword: string;\n forceChangeOnNextLogin: boolean;\n}\n\nexport interface AdminResetPasswordResult {\n status: 'PASSWORD_RESET';\n userId: string;\n forceChangeOnNextLogin: boolean;\n credentialVersion: number;\n sessionsRevoked: boolean;\n removedLoginAttemptKeys: number;\n}\n\n/**\n * 管理员重置租户内用户的本地登录口令。\n *\n * 该端点属于专用凭据流:密码只存在于 HTTPS 请求体,不经过 Model 元数据、\n * Action 参数、日志或返回值。调用方仍须避免把密码放进 argv。\n */\nexport async function adminResetPassword(\n http: AxiosInstance,\n input: AdminResetPasswordRequest,\n): Promise<AdminResetPasswordResult> {\n const response = await http.post('/api/admin/users/password-reset', input);\n const payload = response.data?.data ?? response.data;\n return payload as AdminResetPasswordResult;\n}\n","import WebSocket from 'ws';\nimport { CliError, CliErrorCode } from '../errors';\nimport type {\n StreamCommandEnvelope,\n StreamEventEnvelope,\n ConnectionMessage,\n ControlChannelEnvelope,\n} from './protocol';\n\nexport type MessageClass = 'control' | 'event' | 'connection';\n\n/** WS 入站消息三分:控制通道(channel='control')优先于流事件(streamId+seq),其余为连接层。 */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function classifyMessage(parsed: any): MessageClass {\n if (parsed?.channel === 'control') return 'control';\n if (parsed?.streamId && typeof parsed.seq === 'number') return 'event';\n return 'connection';\n}\n\nexport interface WsClientOpts {\n endpoint: string; // wss://host/ws/conversation\n getAccessToken: () => Promise<string | null>;\n onTokenExpiring?: () => Promise<void>; // 后台 refresh\n heartbeatMs?: number;\n}\n\ntype Handler =\n | { kind: 'event'; fn: (ev: StreamEventEnvelope) => void }\n | { kind: 'connection'; fn: (msg: ConnectionMessage) => void }\n | { kind: 'control'; fn: (ev: ControlChannelEnvelope) => void };\n\nexport class WsClient {\n private ws?: WebSocket;\n private handlers: Handler[] = [];\n private heartbeatTimer?: NodeJS.Timeout;\n private ready = false;\n private sessionId?: string;\n /** 流进行中断连的持久订阅者(iterator 等终态收口者)。RISK-1:auth 后无持久\n * close handler,断连时上层 await 永久挂死。这里让消费方能感知 socket 死。 */\n private disconnectHandlers = new Set<() => void>();\n /** 由 close()/ensureConnected 主动关闭 → 不广播 disconnect(非异常断连)。 */\n private intentionalClose = false;\n\n constructor(private opts: WsClientOpts) {}\n\n /**\n * 订阅「流进行中 socket 异常断连」(后端重启 / 网络闪断)。主动 close() 不触发。\n * iterator 订阅它 → 断连时 finish()(incomplete 终态),杜绝 await consume 永久挂死。\n */\n onDisconnect(fn: () => void): () => void {\n this.disconnectHandlers.add(fn);\n return () => {\n this.disconnectHandlers.delete(fn);\n };\n }\n\n private notifyDisconnect(): void {\n if (this.intentionalClose) return;\n this.ready = false;\n for (const fn of [...this.disconnectHandlers]) {\n try {\n fn();\n } catch {\n /* 单个订阅者抛错不影响其他 */\n }\n }\n }\n\n async connect(): Promise<void> {\n const tok = await this.opts.getAccessToken();\n if (!tok) {\n throw new CliError({\n code: CliErrorCode.AUTH_FAILED,\n message: 'no access token; run `hcm login`',\n });\n }\n this.ws = new WebSocket(this.opts.endpoint);\n\n return new Promise<void>((resolve, reject) => {\n const settle = (fn: () => void) => {\n off();\n fn();\n };\n const off = () => {\n this.ws?.removeListener('open', onOpen);\n this.ws?.removeListener('message', onMessage);\n this.ws?.removeListener('error', onError);\n this.ws?.removeListener('close', onClose);\n };\n const onOpen = () => {\n this.ws?.send(JSON.stringify({ type: 'auth', token: tok }));\n };\n const onMessage = (data: WebSocket.RawData) => {\n const text = data.toString();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let parsed: any;\n try {\n parsed = JSON.parse(text);\n } catch {\n return;\n }\n\n // 三分:控制通道 vs 流事件 vs 连接层\n switch (classifyMessage(parsed)) {\n case 'control':\n this.dispatchControl(parsed as ControlChannelEnvelope);\n return;\n case 'event':\n this.dispatchEvent(parsed as StreamEventEnvelope);\n return;\n case 'connection':\n break;\n }\n const msg = parsed as ConnectionMessage;\n this.dispatchConnection(msg);\n\n if (msg.type === 'auth_success') {\n this.ready = true;\n this.intentionalClose = false;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.sessionId = (msg as any).sessionId;\n this.startHeartbeat();\n settle(resolve);\n // 装上持久 listener,接管后续流事件 / 连接层消息\n this.ws!.on('message', this.persistentOnMessage);\n // RISK-1:持久 close/error listener——auth 后 settle 的 off() 摘掉了建连期\n // 的 onClose,此后无人感知 socket 死。流进行中异常断连时广播 disconnect,\n // 让 iterator finish() 收口,杜绝 await consume 永久挂死。\n this.ws!.on('close', this.persistentOnClose);\n this.ws!.on('error', this.persistentOnClose);\n } else if (msg.type === 'auth_error') {\n settle(() =>\n reject(\n new CliError({\n code: CliErrorCode.WS_AUTH_ERROR,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n message: `${(msg as any).code}: ${(msg as any).message}`,\n }),\n ),\n );\n } else if (msg.type === 'token_expiring' && this.opts.onTokenExpiring) {\n void this.opts.onTokenExpiring();\n }\n };\n const onError = (err: Error) =>\n settle(() =>\n reject(new CliError({ code: CliErrorCode.NETWORK_ERROR, message: err.message })),\n );\n const onClose = () =>\n settle(() =>\n reject(\n new CliError({ code: CliErrorCode.NETWORK_ERROR, message: 'WS closed before auth' }),\n ),\n );\n this.ws!.on('open', onOpen);\n this.ws!.on('message', onMessage);\n this.ws!.on('error', onError);\n this.ws!.on('close', onClose);\n });\n }\n\n send<P>(cmd: StreamCommandEnvelope<P>): void {\n if (!this.ready || !this.ws) {\n throw new CliError({ code: CliErrorCode.NETWORK_ERROR, message: 'WS not ready' });\n }\n this.ws.send(JSON.stringify(cmd));\n }\n\n onEvent(fn: (ev: StreamEventEnvelope) => void): () => void {\n const h: Handler = { kind: 'event', fn };\n this.handlers.push(h);\n return () => {\n this.handlers = this.handlers.filter((x) => x !== h);\n };\n }\n\n onConnection(fn: (msg: ConnectionMessage) => void): () => void {\n const h: Handler = { kind: 'connection', fn };\n this.handlers.push(h);\n return () => {\n this.handlers = this.handlers.filter((x) => x !== h);\n };\n }\n\n onControl(fn: (ev: ControlChannelEnvelope) => void): () => void {\n const h: Handler = { kind: 'control', fn };\n this.handlers.push(h);\n return () => {\n this.handlers = this.handlers.filter((x) => x !== h);\n };\n }\n\n isReady(): boolean {\n return this.ready;\n }\n\n /**\n * 连接已断(后端重启 / 网络闪断 / 心跳静默失败后 socket 已死)时重建连接。\n * REPL 长开场景每轮发消息前调用——旧连接死掉不应让用户重启 CLI(2026-06-12 用户\n * 实测:后端重启后 REPL 下一条消息报错)。已连接且 socket OPEN 时为 no-op。\n */\n async ensureConnected(): Promise<void> {\n if (this.ready && this.ws && this.ws.readyState === WebSocket.OPEN) return;\n await this.close();\n await this.connect();\n }\n\n getSessionId(): string | undefined {\n return this.sessionId;\n }\n\n async close(): Promise<void> {\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n this.ready = false;\n this.intentionalClose = true; // 主动关 → persistentOnClose 不广播 disconnect\n if (this.ws) {\n this.ws.removeListener('message', this.persistentOnMessage);\n this.ws.removeListener('close', this.persistentOnClose);\n this.ws.removeListener('error', this.persistentOnClose);\n if (this.ws.readyState === WebSocket.OPEN) this.ws.close();\n }\n }\n\n /** 流进行中 socket 异常断连(非主动 close)→ 广播 disconnect 让消费方收口。 */\n private persistentOnClose = (): void => {\n this.notifyDisconnect();\n };\n\n private persistentOnMessage = (data: WebSocket.RawData): void => {\n const text = data.toString();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let parsed: any;\n try {\n parsed = JSON.parse(text);\n } catch {\n return;\n }\n switch (classifyMessage(parsed)) {\n case 'control':\n this.dispatchControl(parsed as ControlChannelEnvelope);\n return;\n case 'event':\n this.dispatchEvent(parsed as StreamEventEnvelope);\n return;\n case 'connection':\n break;\n }\n const msg = parsed as ConnectionMessage;\n this.dispatchConnection(msg);\n if (msg.type === 'token_expiring' && this.opts.onTokenExpiring) {\n void this.opts.onTokenExpiring();\n }\n };\n\n private startHeartbeat(): void {\n const ms = this.opts.heartbeatMs ?? 30_000;\n this.heartbeatTimer = setInterval(() => {\n try {\n this.ws?.send(JSON.stringify({ type: 'ping' }));\n } catch {\n /* swallow heartbeat send errors */\n }\n }, ms);\n }\n\n private dispatchEvent(ev: StreamEventEnvelope): void {\n for (const h of this.handlers) if (h.kind === 'event') h.fn(ev);\n }\n\n private dispatchConnection(msg: ConnectionMessage): void {\n for (const h of this.handlers) if (h.kind === 'connection') h.fn(msg);\n }\n\n private dispatchControl(ev: ControlChannelEnvelope): void {\n for (const h of this.handlers) if (h.kind === 'control') h.fn(ev);\n }\n}\n","import type { AttachmentMeta } from '../documents';\nimport { randomUUID } from 'node:crypto';\nimport type { WsClient } from './ws-client';\nimport type {\n StreamEventEnvelope,\n StreamCommandEnvelope,\n MessageCreatePayload,\n ControlChannelEnvelope,\n} from './protocol';\nimport { buildInterrupt, type StreamCommand } from './control-channel';\n\nexport interface SendMessageOpts {\n conversationId?: string; // 不传 = 新建\n agentKey?: string;\n agentId?: string; // direct-ops mode: bypass conversation→agent lookup\n pageContext?: Record<string, unknown>;\n /**\n * confirm/ask 决策回发后继续消费 resume 流到真终态(Task B3)。\n *\n * v4 协议:confirm 触发时原流以 ROOT message.stop 终态(HALT 语义);决策回发后后端\n * resume 续推事件。开启本开关后终态规则变为:\n * - ROOT message.stop 且无未决交互 → 终态(含全程无交互的普通流)\n * - ROOT message.stop 且有未决交互 → hold(HALT / 串联 confirm 的中间 stop)\n * - `control.interaction.resolved(phase=TERMINAL)` 清空未决集且曾 hold 过 ROOT stop\n * → 终态兜底(TERMINAL 是 InteractionSession 生命周期事件,必达;防 resume 段缺\n * ROOT message.stop 的后端回归挂死 CLI)\n * 仅在调用方会回发决策(--on-confirm / --on-ask / 交互式确认)时开启;默认关闭,\n * 行为与历史完全一致。\n */\n holdUntilInteractionTerminal?: boolean;\n /**\n * 重新生成模式(ADR-089):不发 message.create,改发 `response.regenerate`\n * (payload {fromSeq})。fromSeq = 被重生成 assistant 段起点的 journal seq\n * (LLM_DONE;timeline chat-text item 的 seq 即它)。后端 append SEGMENT_SUPERSEDED\n * marker 后用原 user message 重跑 loop,新流事件 requestId 回显本命令 commandId\n * ——iterator 过滤与终态规则完全复用。必须配合已存在的 conversationId。\n */\n regenerateFromSeq?: number;\n /** 附件元数据(issue #64):已上传文档随消息发送,后端 parseAttachments 同形态。 */\n attachments?: AttachmentMeta[];\n}\n\nexport interface MessageStreamHandle {\n conversationId: string;\n commandId: string;\n events: AsyncIterable<StreamEventEnvelope>;\n /**\n * 客户端中断当前流(ADR-085)。\n * 发送 `stream.cancel` command 到后端;后端会回 `response.interrupted` 作 iterator 终态。\n * 调用方仍需等 iterator 走完才能确认终止。\n */\n cancel(reason?: string): void;\n /**\n * 订阅本 conversation 的带外控制通道事件(control.interaction.request / resolved)。\n * 仅转发 `ev.conversationId === conversationId` 的事件;返回取消订阅函数。\n */\n onControl(fn: (ev: ControlChannelEnvelope) => void): () => void;\n /**\n * 回发 client→server 控制命令(approval.confirm / approval.reject / interaction.answer)。\n * 由 control-channel 的 build* 构造,透传到 ws。\n */\n sendCommand(cmd: StreamCommand): void;\n}\n\nexport async function sendMessageAndStream(\n ws: WsClient,\n prompt: string,\n opts: SendMessageOpts = {},\n): Promise<MessageStreamHandle> {\n const conversationId = opts.conversationId ?? randomUUID();\n const commandId = randomUUID();\n const queue: StreamEventEnvelope[] = [];\n const waiters: Array<(ev: IteratorResult<StreamEventEnvelope>) => void> = [];\n let done = false;\n const seenEventIds = new Set<string>();\n\n // B3 · hold 模式状态:未决交互集(值 = 是否已收到 RESUMED 受理)+ 是否 hold 过\n // ROOT stop(HALT 已发生的标记)。B4 升级为 Map:接管链语义需要区分「已 RESUMED」。\n const hold = !!opts.holdUntilInteractionTerminal;\n const pendingInteractions = new Map<string, { resumed: boolean }>();\n let sawHeldRootStop = false;\n let offControlInternal: (() => void) | undefined;\n\n const finish = (): void => {\n if (done) return;\n done = true;\n while (waiters.length) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-explicit-any\n waiters.shift()!({ value: undefined as any, done: true });\n }\n off();\n offControlInternal?.();\n offDisconnect?.();\n };\n\n // RISK-1:流进行中 socket 异常断连(后端重启 / 网络闪断)→ finish()(incomplete 终态),\n // 杜绝 await consume 永久挂死。queue 中已收事件在 finish 前被消费方吐尽,断后置 done。\n // onDisconnect 可选(旧 WsClient / 测试 stub 无此方法时跳过,不破坏向后兼容)。\n const offDisconnect = ws.onDisconnect?.(() => finish());\n\n const off = ws.onEvent((ev) => {\n if (done) return;\n if (ev.requestId !== commandId) return;\n // v4 去重必须按 eventId,不能按 seq 单调(旧 v3 逻辑)。v4 的 ephemeral 事件\n // (LlmDelta text/thinking 等)共享上一个 material 的 seq(不递增),seq<=lastSeq\n // 会把 text delta + 终态事件全部误杀 → 空白回答 + 等不到 response.completed 而 timeout。\n if (ev.eventId && seenEventIds.has(ev.eventId)) return;\n if (ev.eventId) seenEventIds.add(ev.eventId);\n if (waiters.length) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n waiters.shift()!({ value: ev, done: false });\n } else {\n queue.push(ev);\n }\n\n // v3 残余终态(completed/failed/interrupted)只在老路径出现,老路径无 resume → 一律立即终态。\n if (\n ev.type === 'response.completed' ||\n ev.type === 'response.failed' ||\n ev.type === 'response.interrupted'\n ) {\n finish();\n return;\n }\n // v4 原生终态:ROOT message.stop(parentToolUseId 空)= turn 结束,与 Web v4 同。\n // 某些路径(如 hcm_respond_directly 短路)后端不发 v3 兼容残余 response.completed,\n // 不以 message.stop 兜底则 iterator 永不结束而 timeout。子流 message.stop(parentToolUseId 非空)不算终态。\n if (\n ev.type === 'message.stop' &&\n !(ev.payload as { parentToolUseId?: string } | undefined)?.parentToolUseId\n ) {\n if (!hold || pendingInteractions.size === 0) {\n finish();\n } else {\n // HALT(或串联 confirm 的中间 stop):有未决交互 → 不终态,等 resume / TERMINAL。\n sawHeldRootStop = true;\n }\n }\n });\n\n // hold 模式:内部订阅 control 通道维护未决交互集。TERMINAL resolved 清空 pending 且\n // 曾 hold 过 ROOT stop → 终态兜底(resume 段内容此刻已推完,queue 中事件仍会被吐尽)。\n if (hold) {\n offControlInternal = ws.onControl((cev) => {\n if (done || cev.conversationId !== conversationId) return;\n const p = cev.payload as { interactionId?: string; phase?: string } | undefined;\n if (!p?.interactionId) return;\n if (cev.type === 'control.interaction.request') {\n // B4 · 接管链语义(bay4 真流实测):interaction 被嵌套 confirm「接管」后\n // (前序已 RESUMED、其 resume 轮又 HALTED 交给新 confirm),后端按设计\n // 不再为前序发 TERMINAL(HALTED 分支不发)。新 request 到达 = 已 RESUMED\n // 的前序终局由新 interaction 接管 → 从 pending 清除,否则链式 confirm 下\n // 终态条件 pending==0 永不满足而挂死。未 RESUMED 的并存 interaction\n // (用例⑤)各有自己的 TERMINAL,不在清理范围。\n for (const [id, st] of pendingInteractions) {\n if (st.resumed) pendingInteractions.delete(id);\n }\n pendingInteractions.set(p.interactionId, { resumed: false });\n } else if (cev.type === 'control.interaction.resolved') {\n if (p.phase === 'RESUMED') {\n const st = pendingInteractions.get(p.interactionId);\n if (st) st.resumed = true;\n } else if (p.phase === 'TERMINAL') {\n pendingInteractions.delete(p.interactionId);\n if (pendingInteractions.size === 0 && sawHeldRootStop) finish();\n }\n }\n });\n }\n\n const cmd: StreamCommandEnvelope<MessageCreatePayload | { fromSeq: number }> =\n opts.regenerateFromSeq != null\n ? {\n // regenerate:同 conversation 重跑被废弃段,事件 requestId 回显本 commandId。\n protocolVersion: '4.0',\n commandId,\n conversationId,\n type: 'response.regenerate',\n timestamp: Date.now(),\n payload: { fromSeq: opts.regenerateFromSeq },\n }\n : {\n // v4 reference client:后端 ADR-118 起无条件推 v4,此字段是诚实声明(不改变后端行为)。\n protocolVersion: '4.0',\n commandId,\n conversationId,\n type: 'message.create',\n timestamp: Date.now(),\n payload: {\n content: prompt,\n agentKey: opts.agentKey,\n directOpsAgentId: opts.agentId,\n pageContext: opts.pageContext,\n attachments: opts.attachments?.length ? opts.attachments : undefined,\n },\n };\n ws.send(cmd);\n\n const events: AsyncIterable<StreamEventEnvelope> = {\n [Symbol.asyncIterator]: () => ({\n next: () =>\n new Promise((resolve) => {\n if (queue.length) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n resolve({ value: queue.shift()!, done: false });\n } else if (done) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n resolve({ value: undefined as any, done: true });\n } else {\n waiters.push(resolve);\n }\n }),\n }),\n };\n\n const cancel = (_reason: string = 'USER_CANCEL'): void => {\n try {\n // response.interrupt — 后端唯一认的打断命令(StreamCommandType.RESPONSE_INTERRUPT)。\n // 🔴 旧版发 v3.2 的 stream.cancel:后端命令枚举里没有它——cancel 一直是空操作\n // (Esc/Ctrl+C 打断踢空,2026-06-12 用户终验钉出)。统一走 buildInterrupt,消除\n // 与 control-channel 手搓 envelope 的 protocolVersion 不一致(白盒审计 RISK 文档债)。\n ws.send(buildInterrupt(conversationId));\n } catch {\n /* ws 已关闭, 忽略 */\n }\n };\n\n const onControl = (fn: (ev: ControlChannelEnvelope) => void): (() => void) =>\n ws.onControl((ev) => {\n if (ev.conversationId === conversationId) fn(ev);\n });\n\n // 回发命令(approval.* / interaction.answer)的 type 已纳入 StreamCommandType、protocolVersion\n // 收窄为 '3.0',StreamCommand 结构兼容 StreamCommandEnvelope,无需 as 转换。\n const sendCommand = (command: StreamCommand): void => ws.send(command);\n\n return { conversationId, commandId, events, cancel, onControl, sendCommand };\n}\n","/**\n * control-channel — 控制通道(channel='control')解析 + 回发命令构造。\n * 回发走 StreamCommandType(已坐实三方一致:后端 ConversationWebSocketHandler.dispatch /\n * 前端 useAIChat / 协议文档 stream-protocol.md §4),不用死枚举 control.interaction.decision。\n */\nimport { randomUUID } from 'node:crypto';\n\nexport interface InteractionRequest {\n kind: 'CONFIRM' | 'ASK';\n interactionId: string;\n summary: string;\n originAgentPath: string;\n ttlSeconds: number;\n // CONFIRM 专属\n riskLevel?: string;\n toolName?: string;\n argsPreview?: Record<string, unknown>;\n // ASK 专属\n question?: string;\n choices?: string[];\n}\n\nexport interface InteractionResolved {\n interactionId: string;\n phase: 'RESUMED' | 'TERMINAL' | string;\n}\n\nexport interface StreamCommand {\n protocolVersion: '3.0';\n commandId: string;\n conversationId: string;\n type:\n | 'approval.confirm'\n | 'approval.reject'\n | 'interaction.answer'\n // 流中途控制(P0 S-interrupt / S-steer):cooperative cancel / 注入下一 round SystemMessage\n | 'response.interrupt'\n | 'response.steer';\n timestamp: number;\n payload: Record<string, unknown>;\n}\n\nexport function parseInteractionRequest(payload: Record<string, unknown>): InteractionRequest {\n return {\n kind: payload.type as 'CONFIRM' | 'ASK',\n interactionId: payload.interactionId as string,\n summary: (payload.summary as string) || '',\n originAgentPath: (payload.originAgentPath as string) || '',\n ttlSeconds: (payload.ttlSeconds as number) || 0,\n riskLevel: payload.riskLevel as string | undefined,\n toolName: payload.toolName as string | undefined,\n argsPreview: payload.argsPreview as Record<string, unknown> | undefined,\n question: payload.question as string | undefined,\n choices: payload.choices as string[] | undefined,\n };\n}\n\nexport function parseInteractionResolved(payload: Record<string, unknown>): InteractionResolved {\n return {\n interactionId: payload.interactionId as string,\n phase: (payload.phase as string) || '',\n };\n}\n\n// timestamp 由调用点用 Date.now() 注入;测试只校验 type/conversationId/payload,不校验具体时间戳。\nfunction envelope(\n type: StreamCommand['type'],\n conversationId: string,\n payload: Record<string, unknown>,\n): StreamCommand {\n return { protocolVersion: '3.0', commandId: randomUUID(), conversationId, type, timestamp: Date.now(), payload };\n}\n\nexport function buildConfirm(confirmationId: string, conversationId: string): StreamCommand {\n return envelope('approval.confirm', conversationId, { confirmationId });\n}\n\nexport function buildReject(confirmationId: string, conversationId: string, reason?: string): StreamCommand {\n return envelope('approval.reject', conversationId, reason ? { confirmationId, reason } : { confirmationId });\n}\n\nexport function buildAnswer(\n interactionId: string,\n conversationId: string,\n answer: string,\n choiceIndex?: number,\n): StreamCommand {\n return envelope('interaction.answer', conversationId, { interactionId, answer, choiceIndex: choiceIndex ?? null });\n}\n\n/**\n * 流中途打断(cooperative cancel)。后端收到后停止当前 round,流以 ROOT message.stop\n * (payload.stopReason==\"interrupted\")终结。payload 为空对象 {}(后端契约已核实)。\n */\nexport function buildInterrupt(conversationId: string): StreamCommand {\n return envelope('response.interrupt', conversationId, {});\n}\n\n/**\n * 流中途引导(steer)。后端入队 content,下一 round 注入 SystemMessage;无任何 ack 事件\n * (v3 ack 已退役),流正常完成。payload 为 {content}(后端契约已核实)。\n */\nexport function buildSteer(content: string, conversationId: string): StreamCommand {\n return envelope('response.steer', conversationId, { content });\n}\n","/** 委派类工具名(对齐 Web constants.ts::isDelegationToolName)。hcm_handoff 为历史别名。 */\nconst DELEGATION_TOOLS = new Set(['hcm_delegate', 'hcm_handoff']);\nexport function isDelegationToolName(name: string): boolean {\n return DELEGATION_TOOLS.has(name);\n}\n","/**\n * v4-reducer — 复刻 hcm_nx_cloud useChatItemsStreamV4.ts::createV4Reducer 的 fold 状态机,\n * 但输出端换成 CLI 流式语义事件(V4RenderSink)。终端是 append-only 流,不能像 Web 那样\n * 回填 item,故 tool 行等 block.stop(args 齐)才发 onToolStarted。\n * 单层委派模型(与 Web 同限制):currentParentToolUseId 单值;多级嵌套靠 renderer depth counter。\n */\nimport { isDelegationToolName } from './delegation-constants';\nimport { truncateDisplay } from './display-width';\n\nexport interface V4RenderSink {\n onText(text: string, parentToolUseId: string | null): void;\n onThinkingDelta(text: string, parentToolUseId: string | null): void;\n onThinkingDone(parentToolUseId: string | null): void;\n onToolStarted(callId: string, toolName: string, argsPreview: string, parentToolUseId: string | null): void;\n /**\n * @param nextSteps G5 · 工具失败时从 result envelope 解析出的 ADR-058 路标(`error.nextSteps`\n * 数组 / 顶层 `next_step`)。成功或无路标时 undefined。供 host 在失败行下透出下一步指引。\n * @param toolName #1930 · 工具名。优先取 block.start 注册值;缓存短路 no-op 等无 block.start\n * 的事件由 reducer 从 result 文本兜底解析(如「hcm_describe_tool (缓存命中)」),避免 host\n * 裸显示 `?`。仍解析不出时 undefined,host 自行兜底。\n */\n onToolResult(\n callId: string,\n success: boolean,\n durMs: number,\n childConversationId: string | null,\n parentToolUseId: string | null,\n nextSteps?: string[],\n toolName?: string,\n ): void;\n onContentPart(part: Record<string, unknown>, parentToolUseId: string | null): void;\n /**\n * 用户中途纠偏在轮界被真正注入(v4 `steering.applied` 顶层事件,W1a-2)。\n * 仅注入时到达——客户端禁止在发 response.steer 时乐观本地显示「已纠偏」。\n */\n onSteeringApplied(contents: string[], round: number): void;\n onDelegationStart(agentKey: string, parentToolUseId: string): void;\n /**\n * 委派收口。由 hcm_delegate 自身的 tool.result 驱动(后端时序保证:子流 message.stop\n * 先到、父 tool.result 紧随其后,见 WebSocketRuntimeEventProjector.finishChildStreamIfAny)\n * ——因此能携带委派整体 success / 耗时 / 失败路标(2026-06-11 review C1/I1:旧版由子\n * message.stop 驱动拿不到 success,失败委派被静默渲染成功)。\n *\n * @param nextSteps 委派失败时的 ADR-058 路标;成功为 undefined。\n */\n onDelegationEnd(parentToolUseId: string, success: boolean, durMs: number, nextSteps?: string[]): void;\n /**\n * 委派「暂停待应答」(非失败):specialist 在子流内调 hcm_ask_user / 触发风险确认时,\n * 后端按 HALT 语义给 hcm_delegate 的 tool.result 发 transport `success=false`,但其\n * envelope 实为 `ok:true` + `INTERACTION_REQUIRED`/`CONFIRMATION_REQUIRED` 标记\n * (后端 InteractionSuspendMiddleware / RiskConfirmationMiddleware)。这不是失败——\n * 用户应答后委派会 RESUME(同 parentToolUseId 再来一段 message.start)续跑到真终态。\n *\n * 消费端据此:保留委派块不收口、累计本段耗时,等 RESUME 续同一块——避免把暂停\n * 渲染成红色「委派失败」、并避免暂停→续跑被计为两次委派(CLI append-only 无法回填)。\n * @param durMs 暂停前本段执行耗时(累计进委派块总耗时)。\n */\n onDelegationPaused(parentToolUseId: string, durMs: number): void;\n onTerminal(stopReason: string, tokens: number): void;\n}\n\n/**\n * 判定一条 hcm_delegate 的 tool.result(transport `success=false`)实为「暂停待应答」而非失败。\n *\n * 后端 wire 接缝不一致:HALT 时 executor 结果 `success=true`+`haltConversation`,但投影到\n * delegate 的 tool.result `success` 字段为 false;其 resultData envelope 仍诚实保留 `ok:true`\n * + summary/data 带 `INTERACTION_REQUIRED`(ASK)/`CONFIRMATION_REQUIRED`(CONFIRM)标记。\n * 以「envelope ok===true 且含交互标记」为准绳——纯结构信号,不误伤真失败(真失败 ok:false)。\n */\nexport function detectDelegationPause(resultData: unknown): boolean {\n if (!resultData || typeof resultData !== 'object') return false;\n const rd = resultData as Record<string, unknown>;\n let env: Record<string, unknown> | null = null;\n const raw = rd.result;\n if (typeof raw === 'string') {\n try {\n const o = JSON.parse(raw);\n env = o && typeof o === 'object' ? (o as Record<string, unknown>) : null;\n } catch {\n env = null;\n }\n } else if (raw && typeof raw === 'object') {\n env = raw as Record<string, unknown>;\n } else {\n env = rd;\n }\n if (!env || env.ok !== true) return false;\n const text = [env.summary, env.data].filter((x) => typeof x === 'string').join(' ');\n return text.includes('INTERACTION_REQUIRED') || text.includes('CONFIRMATION_REQUIRED');\n}\n\n/**\n * #1930 · 取 tool.result 的原始结果文本(用于无 envelope 的纯字符串结果分类)。\n *\n * wire 形态 {@code resultData = { result: \"<string>\" }}(WS ToolResultPayload);也兼容\n * resultData 本身即字符串。非字符串结果(结构化对象)返回空串——这些走正常 envelope 路径,\n * 不属本函数关心的「控制信号 / no-op 短路」纯文本类。\n */\nfunction resultText(resultData: unknown): string {\n if (typeof resultData === 'string') return resultData;\n if (resultData && typeof resultData === 'object') {\n const raw = (resultData as Record<string, unknown>).result;\n if (typeof raw === 'string') return raw;\n }\n return '';\n}\n\n/**\n * #1930 Bug B · 判定一条**直接工具**(非委派)的 tool.result 实为「待确认 / 待应答」控制信号,\n * 而非工具执行结果。\n *\n * 后端写动作命中确认门 / 交互门时,会先发一条 transport `success=false` 的 TOOL_COMPLETED,其\n * result 是纯文本前缀 {@code \"CONFIRMATION_REQUIRED: ...\"} / {@code \"INTERACTION_REQUIRED: ...\"}\n * (非 JSON envelope),同时另发一条 {@code control.interaction.request} 让 host 弹确认卡。该 tool.result\n * 对展示流是冗余的——若按普通工具渲染会变成红色 `⚙ ? ✗ 0ms` 假失败并计入失败统计(实测用户困惑)。\n *\n * 以「结果文本以这两个标记**起始**」为准绳(强协议信号,正常工具结果不会以此开头),故 host 可安全\n * 抑制该行,把待确认态交给 confirm 卡承载。委派场景的同类暂停由 {@link detectDelegationPause} 处理,\n * 二者互不重叠(委派结果是 envelope,直接工具是纯文本前缀)。\n */\nexport function detectDirectInteractionPending(resultData: unknown): boolean {\n const text = resultText(resultData).trimStart();\n return text.startsWith('CONFIRMATION_REQUIRED') || text.startsWith('INTERACTION_REQUIRED');\n}\n\n/**\n * #1930 Bug A · 为无 block.start 注册名的 tool.result 从结果文本兜底解析有意义的展示名。\n *\n * 当前唯一已知来源:ADR-095 catalog 缓存短路 no-op——后端直接发 TOOL_COMPLETED(无 TOOL_STARTED),\n * result 为 {@code \"[ADR-095 catalog cache hit] 工具 <toolName> 已在本 sub-session ...\"}。解析出工具名\n * 时返回 {@code \"<toolName> (缓存命中)\"},解析不出则返回 {@code \"缓存命中\"}。其他结果返回 undefined\n * (host 保留原 `?` 兜底给真未知事件,不强行编造名字)。\n */\nexport function deriveNamelessToolLabel(resultData: unknown): string | undefined {\n const text = resultText(resultData);\n if (text.includes('catalog cache hit')) {\n const m = text.match(/工具\\s+(\\w+)/);\n return m ? `${m[1]} (缓存命中)` : '缓存命中';\n }\n return undefined;\n}\n\n/**\n * #1949 · 从确认门 / 交互门占位结果文本解析被挂起的工具名。\n *\n * 后端 {@code RiskConfirmationMiddleware} 挂起时发的占位 TOOL_COMPLETED,其 result 为纯文本\n * {@code \"CONFIRMATION_REQUIRED: 等待用户确认 tool=<name>, confirmationId=..., scopeKey=...\"}\n * (INTERACTION_REQUIRED 亦可能带 {@code tool=})。用户确认后被 resume 的工具,其真结果\n * {@code tool.result} 可能先于(或缺失)resume 段 {@code block.start} 抵达消费端 → {@code toolNameByCallId}\n * 无注册名 → 裸显示 `?`(#1949)。这里在抑制占位结果时解析出工具名,供 resume 真结果按 callId 兜底取名。\n * 解析不出返回 undefined。\n */\nexport function parseConfirmToolName(resultData: unknown): string | undefined {\n const m = resultText(resultData).match(/\\btool=([^,\\s]+)/);\n return m ? m[1] : undefined;\n}\n\n/**\n * G5 · 从 tool.result 的 resultData 防御性提取 ADR-058 `next_step` 路标。\n *\n * 后端 wire:`resultData = { result: \"<envelope JSON 字符串>\" }`,失败 envelope 形如\n * `{ ok:false, error:{ code, message, nextSteps:[...] } }`;部分执行器平铺顶层 `next_step`\n * (可为 string / string[] / {message,...} map)。这里兼容多形态,任一解析失败都静默返回\n * undefined(非 JSON 纯文本 result 不炸)。\n */\nexport function extractNextSteps(resultData: unknown): string[] | undefined {\n if (!resultData || typeof resultData !== 'object') return undefined;\n const rd = resultData as Record<string, unknown>;\n // wire 形态:{ result: \"<json string>\" };也兼容 resultData 本身即结构化对象。\n let env: Record<string, unknown> | null = null;\n const raw = rd.result;\n if (typeof raw === 'string') {\n try {\n const o = JSON.parse(raw);\n env = o && typeof o === 'object' ? (o as Record<string, unknown>) : null;\n } catch {\n env = null;\n }\n } else if (raw && typeof raw === 'object') {\n env = raw as Record<string, unknown>;\n } else {\n env = rd;\n }\n if (!env) return undefined;\n\n const collect = (v: unknown): string[] => {\n if (Array.isArray(v)) return v.map((x) => String(x)).filter((s) => s.length > 0);\n if (typeof v === 'string' && v.length > 0) return [v];\n if (v && typeof v === 'object') {\n const m = (v as Record<string, unknown>).message;\n if (typeof m === 'string' && m.length > 0) return [m];\n }\n return [];\n };\n\n const error = env.error;\n const fromError =\n error && typeof error === 'object'\n ? collect((error as Record<string, unknown>).nextSteps).concat(\n collect((error as Record<string, unknown>).next_step),\n )\n : [];\n const fromTop = collect(env.nextSteps).concat(collect(env.next_step));\n const all = [...fromError, ...fromTop];\n return all.length > 0 ? all : undefined;\n}\n\ninterface V4BlockMeta {\n blockType: string;\n toolName?: string;\n callId?: string;\n parentToolUseId: string | null;\n jsonBuffer: string;\n}\n\nexport function createV4Reducer(sink: V4RenderSink) {\n const blockMap = new Map<number, V4BlockMeta>();\n const toolNameByCallId = new Map<string, string>();\n // #1949 · 确认门通过后被 resume 的直接工具:其 resume 段 block.start 可能未抵达消费端(只到\n // tool.result)→ toolNameByCallId 无注册名 → 裸显示 `?`。挂起时占位结果文本带 `tool=<name>`,\n // 在抑制该占位结果时按 callId 暂存,供 resume 真结果兜底取名(见 tool.result case)。\n const confirmToolNameByCallId = new Map<string, string>();\n // 嵌套委派归属栈(2026-06-11 review I2):旧单值 currentParent 在 d1 子流内再委派 d2 时,\n // d2 的 message.stop 会把归属误归零 → d1 剩余子流文本被当主流程平铺。栈语义:push on\n // 子 message.start,pop on 子 message.stop,currentParent 恒为栈顶。\n const parentStack: string[] = [];\n let currentParent: string | null = null;\n\n const parentOf = (payloadParent?: unknown): string | null =>\n (typeof payloadParent === 'string' && payloadParent) ? payloadParent : currentParent;\n\n // 按显示宽度截断(CJK=2 列):单 value ≤40 列防 reasoning 类长参数霸行,总预览 ≤96 列\n // 保证「⚙ name preview ✓ Xms」在 120 列内单行收口(旧按字符数 slice(120) 中文实占 240 列必折行)。\n function buildArgsPreview(args: Record<string, unknown>): string {\n const preview = Object.entries(args)\n .map(([k, v]) => {\n const raw = typeof v === 'object' ? JSON.stringify(v) : String(v);\n return `${k}=${truncateDisplay(raw, 40)}`;\n })\n .join(' · ');\n return truncateDisplay(preview, 96);\n }\n function parseJson(s: string): Record<string, unknown> | null {\n try { const o = JSON.parse(s); return o && typeof o === 'object' ? o : null; } catch { return null; }\n }\n\n function handle(type: string, payload: Record<string, unknown>): boolean {\n switch (type) {\n case 'message.start': {\n const p = payload.parentToolUseId;\n if (typeof p === 'string' && p) {\n parentStack.push(p);\n currentParent = p;\n sink.onDelegationStart((payload.agentKey as string) || 'specialist', p);\n } else {\n // ROOT message:归属复位(resume 等场景防御性清栈)。\n parentStack.length = 0;\n currentParent = null;\n }\n return true;\n }\n case 'block.start': {\n const index = payload.index as number;\n const blockType = (payload.blockType as string) || 'text';\n const toolName = (payload.toolName as string) || undefined;\n const callId = (payload.callId as string) || undefined;\n blockMap.set(index, { blockType, toolName, callId, parentToolUseId: parentOf(payload.parentToolUseId), jsonBuffer: '' });\n if (blockType === 'tool_use' && callId && toolName) toolNameByCallId.set(callId, toolName);\n // CLI:tool 行等 block.stop(args 齐)才发 onToolStarted;此处不输出。\n return true;\n }\n case 'block.delta': {\n const index = payload.index as number;\n const meta = blockMap.get(index);\n const parent = meta?.parentToolUseId ?? currentParent;\n switch (payload.deltaType) {\n case 'text_delta': sink.onText((payload.text as string) || '', parent); return true;\n case 'thinking_delta': sink.onThinkingDelta((payload.text as string) || '', parent); return true;\n case 'input_json_delta': if (meta) meta.jsonBuffer += (payload.partialJson as string) || ''; return true;\n case 'content_part': if (meta) meta.jsonBuffer += (payload.partialJson as string) || ''; return true;\n default: return true;\n }\n }\n case 'block.stop': {\n const index = payload.index as number;\n const meta = blockMap.get(index);\n blockMap.delete(index);\n if (!meta) return true;\n if (meta.blockType === 'thinking') { sink.onThinkingDone(meta.parentToolUseId); return true; }\n if (meta.blockType === 'tool_use' && meta.callId) {\n const args = meta.jsonBuffer ? parseJson(meta.jsonBuffer) ?? {} : {};\n if (meta.toolName && isDelegationToolName(meta.toolName)) return true; // delegation 由 message.start / tool.result 渲染\n sink.onToolStarted(meta.callId, meta.toolName || '?', buildArgsPreview(args), meta.parentToolUseId);\n return true;\n }\n if (meta.blockType === 'content_part' && meta.jsonBuffer) {\n const part = parseJson(meta.jsonBuffer)?.part;\n if (part && typeof part === 'object') {\n const p = part as Record<string, unknown>;\n // fileDownload content_part({fileName, downloadUrl})→ 渲染为可点的绝对签名链接,\n // 点击由浏览器打开(ADR-140,免 Bearer);CLI 不再自动落盘(用户要求点击打开即可)。\n sink.onContentPart(p, meta.parentToolUseId);\n }\n return true;\n }\n return true;\n }\n case 'tool.result': {\n const callId = payload.callId as string;\n if (!callId) return true;\n const toolName = toolNameByCallId.get(callId);\n const success = payload.success !== false;\n // G5:失败才解析 next_step 路标(成功路径不解析,省开销)。\n const nextSteps = success ? undefined : extractNextSteps(payload.resultData);\n if (toolName && isDelegationToolName(toolName)) {\n // 委派收口在此驱动(review C1/I1):本事件带委派整体 success/durMs/失败路标,\n // 失败委派不再被静默渲染成功。子流 message.stop 仅做归属弹栈(见 message.stop case)。\n const durMs = (payload.executionTimeMs as number) || 0;\n // transport success=false 但实为「暂停待应答」(envelope ok:true + 交互标记)→ 非失败,\n // 走 onDelegationPaused 保留委派块等 RESUME 续跑;真失败仍走 onDelegationEnd。\n if (!success && detectDelegationPause(payload.resultData)) {\n // 🔴 不 delete toolNameByCallId[callId]:RESUME 后的「真收口」tool.result(同 callId)\n // 仍须被识别为委派——否则它退化成普通 onToolResult,渲染成神秘「⚙ ? ✓ 0ms」工具行。\n sink.onDelegationPaused(callId, durMs);\n } else {\n toolNameByCallId.delete(callId);\n sink.onDelegationEnd(callId, success, durMs, nextSteps);\n }\n return true;\n }\n toolNameByCallId.delete(callId);\n // #1930 Bug B:直接工具的「待确认 / 待应答」控制信号(CONFIRMATION_REQUIRED /\n // INTERACTION_REQUIRED 纯文本前缀)不是工具结果——它对应一条 control.interaction.request,\n // 由 confirm 卡承载可见待确认态。这里抑制,避免渲染成红色 `⚙ ? ✗ 0ms` 假失败 + 计入失败统计。\n if (detectDirectInteractionPending(payload.resultData)) {\n // #1949 · 暂存被挂起工具名,供其被 resume 后的真结果兜底取名(resume 段 block.start\n // 可能未抵达消费端 → 否则 resume 工具行裸显示 `?`)。\n const confirmName = parseConfirmToolName(payload.resultData);\n if (confirmName) confirmToolNameByCallId.set(callId, confirmName);\n return true;\n }\n // #1930 Bug A + #1949:名字优先取 block.start 注册值;缺失时取确认门暂存名(resume 直接工具);\n // 再退回结果文本兜底(缓存短路 no-op);都没有才让 host 显示 `?`。\n const resolvedToolName =\n toolName ?? confirmToolNameByCallId.get(callId) ?? deriveNamelessToolLabel(payload.resultData);\n confirmToolNameByCallId.delete(callId);\n sink.onToolResult(\n callId,\n success,\n (payload.executionTimeMs as number) || 0,\n (payload.childConversationId as string) || null,\n parentOf(payload.parentToolUseId),\n nextSteps,\n resolvedToolName,\n );\n return true;\n }\n case 'steering.applied': {\n const contents = Array.isArray(payload.contents)\n ? (payload.contents as unknown[]).map((c) => String(c)).filter((c) => c.length > 0)\n : [];\n if (contents.length > 0) {\n sink.onSteeringApplied(contents, (payload.round as number) ?? 0);\n }\n return true;\n }\n case 'message.stop': {\n const p = payload.parentToolUseId;\n if (typeof p === 'string' && p) {\n // 子流结束:仅弹栈回外层归属(嵌套委派 d1>d2 时 d2 结束回 d1)。收口渲染由\n // 紧随其后的 delegation tool.result 驱动(带 success,见 tool.result case)。\n const idx = parentStack.lastIndexOf(p);\n if (idx >= 0) parentStack.splice(idx, 1);\n currentParent = parentStack[parentStack.length - 1] ?? null;\n return true;\n }\n parentStack.length = 0;\n currentParent = null;\n blockMap.clear();\n sink.onTerminal((payload.stopReason as string) || 'end_turn', (payload.totalTokens as number) || 0);\n return true;\n }\n default: return false;\n }\n }\n\n return { handle };\n}\n","import type { WsClient } from './ws-client';\nimport { sendMessageAndStream, type SendMessageOpts } from './stream-iterator';\nimport type { StreamEventEnvelope, ResponseFailedPayload } from './protocol';\nimport { createV4Reducer, type V4RenderSink } from './v4-reducer';\nimport { CliError, CliErrorCode } from '../errors';\n\nexport interface OneShotToolCall {\n /** 工具名;委派为 'hcm_delegate'。无法解析时 '?'。 */\n name: string;\n /** 参数预览(reducer 已按显示宽度截断);委派时为目标 agentKey。 */\n argsPreview: string;\n success: boolean;\n elapsedMs: number;\n /** 委派调用标记。 */\n isDelegation?: boolean;\n /** 归属父 tool_use(顶层直接工具为 null)。 */\n parentToolUseId: string | null;\n}\n\nexport interface OneShotResult {\n conversationId: string;\n streamId: string;\n traceId: string | null;\n /** 最终助手回答正文(ROOT 段文本;委派 specialist 的中间文本不计入)。 */\n content: string;\n toolCalls: OneShotToolCall[];\n elapsedMs: number;\n events: StreamEventEnvelope[];\n /** 终态原因(v4 message.stop.stopReason);未见终态时 undefined。 */\n stopReason?: string;\n /** 本轮总 token(v4 message.stop.totalTokens)。 */\n tokens?: number;\n}\n\nexport interface OneShotOpts extends SendMessageOpts {\n collectEvents?: boolean;\n}\n\n/**\n * 非流式单发:消费 v4 事件流并聚合出最终结果。\n *\n * 🔴 与流式路径同源:把事件喂进 {@link createV4Reducer}(与 CLI streaming、Web 端 fold\n * 状态机一致的 v4 语义层),用一个 headless sink 累加 ROOT 文本 + 收集工具调用——而非\n * 手搓第二套聚合。历史 bug:旧实现监听已废弃的 v3 事件名(response.part.delta /\n * response.completed / response.tool.*),后端早改发 v4(block.delta:text_delta /\n * message.stop)→ switch 全不命中 → content 恒空(--no-stream / --output json 拿到空答案)。\n * 语义层复用见 ADR-129 D2:只换「画」的层,聚合逻辑不重复实现。\n *\n * 终态由 stream-iterator 在 ROOT message.stop(或断连)关闭 async iterator 驱动,本函数\n * 无需自判终止。v3 残余 `response.failed`(仅老路径出现)仍显式抛 CliError 保留旧语义。\n */\nexport async function oneShot(\n ws: WsClient,\n prompt: string,\n opts: OneShotOpts = {},\n): Promise<OneShotResult> {\n const t0 = Date.now();\n const { conversationId, events } = await sendMessageAndStream(ws, prompt, opts);\n\n let content = '';\n let streamId = '';\n let traceId: string | null = null;\n let stopReason: string | undefined;\n let tokens: number | undefined;\n const toolCalls: OneShotToolCall[] = [];\n const collected: StreamEventEnvelope[] = [];\n\n // reducer sink 无 block.start→tool.result 的 callId 内联参数,故按 callId 暂存预览 /\n // 委派 agentKey,待 result / delegationEnd 到达时回填。\n const argsPreviewByCallId = new Map<string, string>();\n const agentKeyByCallId = new Map<string, string>();\n\n const sink: V4RenderSink = {\n // ROOT 段文本才是最终回答;specialist 子流文本(parent 非空)是中间产物,不计入 content。\n onText(text, parentToolUseId) {\n if (parentToolUseId === null) content += text;\n },\n onThinkingDelta() {},\n onThinkingDone() {},\n onToolStarted(callId, _toolName, argsPreview) {\n argsPreviewByCallId.set(callId, argsPreview);\n },\n onToolResult(callId, success, durMs, _childConversationId, parentToolUseId, _nextSteps, toolName) {\n toolCalls.push({\n name: toolName ?? '?',\n argsPreview: argsPreviewByCallId.get(callId) ?? '',\n success,\n elapsedMs: durMs,\n parentToolUseId,\n });\n argsPreviewByCallId.delete(callId);\n },\n onContentPart() {},\n onSteeringApplied() {},\n onDelegationStart(agentKey, parentToolUseId) {\n agentKeyByCallId.set(parentToolUseId, agentKey);\n },\n onDelegationEnd(callId, success, durMs) {\n toolCalls.push({\n name: 'hcm_delegate',\n argsPreview: agentKeyByCallId.get(callId) ?? '',\n success,\n elapsedMs: durMs,\n isDelegation: true,\n parentToolUseId: null,\n });\n agentKeyByCallId.delete(callId);\n },\n onDelegationPaused() {},\n onTerminal(reason, tk) {\n stopReason = reason;\n tokens = tk;\n },\n };\n\n const reducer = createV4Reducer(sink);\n\n for await (const ev of events) {\n if (opts.collectEvents) collected.push(ev);\n if (!streamId) streamId = ev.streamId;\n if (!traceId) traceId = ev.traceId;\n\n // v3 残余终态:仅老后端路径出现,v4 无此事件。保留旧语义——失败即抛。\n if (ev.type === 'response.failed') {\n throw new CliError({\n code: CliErrorCode.BUSINESS_RULE_VIOLATION,\n message: (ev.payload as ResponseFailedPayload).message,\n traceId: traceId ?? undefined,\n });\n }\n\n reducer.handle(ev.type, ev.payload as Record<string, unknown>);\n }\n\n return {\n conversationId,\n streamId,\n traceId,\n content,\n toolCalls,\n elapsedMs: Date.now() - t0,\n events: collected,\n stopReason,\n tokens,\n };\n}\n","import * as fs from 'node:fs/promises';\nimport { conversationStateFile, profileDir } from '../config/paths';\n\nexport interface ConversationState {\n lastConversationId?: string;\n lastStreamId?: string;\n lastTraceId?: string;\n lastAgentKey?: string;\n updatedAt: string;\n}\n\nexport async function loadConversationState(profile: string): Promise<ConversationState | null> {\n try {\n const raw = await fs.readFile(conversationStateFile(profile), 'utf-8');\n return JSON.parse(raw) as ConversationState;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw e;\n }\n}\n\nexport async function saveConversationState(\n profile: string,\n state: ConversationState,\n): Promise<void> {\n await fs.mkdir(profileDir(profile), { recursive: true });\n await fs.writeFile(\n conversationStateFile(profile),\n JSON.stringify({ ...state, updatedAt: new Date().toISOString() }, null, 2),\n );\n}\n","import type { AxiosInstance } from 'axios';\n\n/** Default analyst agent id (seeded in HCM at tenant=__SYSTEM__). */\nexport const DEFAULT_AGENT_ID = '019cca00-0000-7000-0000-000000000001';\n\nexport interface CreatedConversation {\n id: string;\n agentId: string;\n title?: string;\n}\n\n/**\n * Create a new conversation in HCM. Required before sending any /ws/conversation\n * message — the WS handler looks up conversation→agentId in DB.\n */\nexport async function createConversation(\n http: AxiosInstance,\n agentId: string = DEFAULT_AGENT_ID,\n): Promise<CreatedConversation> {\n const resp = await http.post('/api/models/ConversationModel', { agentId });\n return {\n id: resp.data.id,\n agentId: resp.data.agentId,\n title: resp.data.title,\n };\n}\n","import type { AxiosInstance } from 'axios';\nimport { truncateDisplay } from './display-width';\n\n/**\n * 续会话回放(CLI 做透轮刀⑤,对标 Claude Code resume 的上下文可见性)。\n *\n * hcm chat 只有显式 --resume 续接 conversation 时才回放历史;否则默认开新会话。\n * 本模块拉\n * timeline(GET /api/models/ConversationModel/{id}/action/timeline,与 Web 回放\n * 同源)折出轻量摘要:最近 N 轮 user/assistant 文本 + 更早轮次计数。\n */\n\n/** timeline 条目(后端 TimelineItemDto 的 CLI 侧投影,只取回放需要的字段)。 */\nexport interface TimelineItemLite {\n kind: string;\n seq: number;\n /** user-msg / steering 的原文。 */\n text?: string | null;\n /** chat-text 的正文。 */\n content?: string | null;\n /** user-msg 附件元数据(#1849 journal 单源透传;issue #64 回放 📎)。 */\n attachments?: Array<{ fileName?: string }> | null;\n}\n\nexport interface ResumeTurn {\n role: 'user' | 'assistant';\n text: string;\n}\n\nexport interface ResumeSummary {\n conversationId: string;\n /** 会话总轮数(user-msg 条目数)。 */\n totalRounds: number;\n /** 最近 maxRounds 轮的 user/assistant 文本(已按显示宽度截断为单行摘要)。 */\n recent: ResumeTurn[];\n}\n\nfunction normalizeTimelineItems(body: unknown): TimelineItemLite[] | null {\n if (Array.isArray(body)) return body as TimelineItemLite[];\n if (\n body\n && typeof body === 'object'\n && Array.isArray((body as { data?: unknown }).data)\n ) {\n return (body as { data: TimelineItemLite[] }).data;\n }\n return null;\n}\n\n/**\n * 拉取 conversation timeline。任何失败(网络/404/结构异常)返回 null——\n * 回放是体验增强,绝不阻断聊天主流程。\n */\nexport async function fetchConversationTimeline(\n http: AxiosInstance,\n conversationId: string,\n): Promise<TimelineItemLite[] | null> {\n try {\n const resp = await http.get(\n `/api/models/ConversationModel/${conversationId}/action/timeline`);\n return normalizeTimelineItems(resp.data);\n } catch {\n return null;\n }\n}\n\n/**\n * 严格拉取 conversation timeline。用于 CLI 显式 inspect 场景:网络/404/结构异常都应暴露,\n * 不能像 resume 体验增强那样静默吞掉。\n */\nexport async function fetchConversationTimelineStrict(\n http: AxiosInstance,\n conversationId: string,\n): Promise<TimelineItemLite[]> {\n const resp = await http.get(\n `/api/models/ConversationModel/${conversationId}/action/timeline`);\n const items = normalizeTimelineItems(resp.data);\n if (!items) {\n throw new Error(`Conversation timeline response is not an array: ${conversationId}`);\n }\n return items;\n}\n\n/**\n * timeline → 续会话摘要。取最近 maxRounds 轮:每轮 = user-msg 原文 + 该轮最后一条\n * chat-text 正文(中间 tools/delegation/steering 等过程条目不进摘要——回顾要的是\n * 「聊到哪了」不是过程重放)。文本截为单行(换行折叠 + 显示宽度截断)。\n */\nexport function buildResumeSummary(\n conversationId: string,\n items: TimelineItemLite[],\n maxRounds: number = 2,\n maxWidth: number = 76,\n): ResumeSummary | null {\n if (!Array.isArray(items) || items.length === 0) return null;\n // 按 user-msg 切轮:每个 user-msg 开启一轮,轮内最后一条 chat-text 是该轮 assistant 收尾。\n interface Round { user: string; assistant: string | null }\n const rounds: Round[] = [];\n for (const it of items) {\n if (it.kind === 'user-msg' && it.text && it.text.trim()) {\n const atts = (it.attachments ?? []).map((a) => a?.fileName).filter(Boolean);\n const suffix = atts.length ? ` 📎 ${atts.join(' · ')}` : '';\n rounds.push({ user: it.text + suffix, assistant: null });\n } else if (it.kind === 'chat-text' && it.content && it.content.trim() && rounds.length > 0) {\n const last = rounds[rounds.length - 1];\n if (last) last.assistant = it.content;\n }\n }\n if (rounds.length === 0) return null;\n\n const oneLine = (s: string): string =>\n truncateDisplay(s.replace(/\\s*\\n\\s*/g, ' ').trim(), maxWidth);\n\n const recent: ResumeTurn[] = [];\n for (const r of rounds.slice(-maxRounds)) {\n recent.push({ role: 'user', text: oneLine(r.user) });\n if (r.assistant) recent.push({ role: 'assistant', text: oneLine(r.assistant) });\n }\n return { conversationId, totalRounds: rounds.length, recent };\n}\n\nexport function findLastAssistantSeq(items: TimelineItemLite[] | null | undefined): number | null {\n const last = [...(items ?? [])].reverse()\n .find((it) => it.kind === 'chat-text' && Number.isInteger(it.seq));\n return last?.seq ?? null;\n}\n","import type { AxiosInstance } from 'axios';\nimport { query } from '../models/query';\n\nexport interface ConversationListItem {\n id?: string;\n title?: string | null;\n summary?: string | null;\n agentId?: string | null;\n messageCount?: number | null;\n totalTokens?: number | null;\n lastActiveAt?: string | null;\n lastMessagePreview?: string | null;\n createTime?: string | null;\n updateTime?: string | null;\n}\n\nexport interface ConversationListResult {\n rows: ConversationListItem[];\n total?: number;\n}\n\nexport async function fetchRecentConversations(\n http: AxiosInstance,\n limit: number = 10,\n): Promise<ConversationListResult> {\n const result = await query<ConversationListItem>(http, 'ConversationModel', {\n filter: { origin: 'USER_CHAT', archived: false },\n fields: [\n 'id',\n 'title',\n 'summary',\n 'agentId',\n 'messageCount',\n 'totalTokens',\n 'lastActiveAt',\n 'lastMessagePreview',\n 'createTime',\n 'updateTime',\n ],\n sort: [{ field: 'lastActiveAt', direction: 'desc' }],\n limit,\n });\n return { rows: result.rows, total: result.total };\n}\n","export const MINI_CONTEXT_FILE = 'hcm.context.json';\nexport const MINI_STATE_DIR = '.hcm';\nexport const MINI_PULL_MANIFEST_FILE = 'mini-manifest.json';\nexport const MINI_REMOTE_ROOT = 'mini_apps';\nexport const MINI_CONTEXT_KIND = 'hcm-mini-app';\nexport const MINI_CONTEXT_VERSION = 1;\n\nexport type MiniSurface = 'embedded' | 'standalone';\nexport type MiniTemplateKind = 'dashboard' | 'multi-page' | 'writable-workbench';\n\nexport interface WorkspaceFileItem {\n id: string;\n workspaceKey: string;\n relativePath: string;\n fileType?: string;\n fileSize?: number;\n lastModified?: number | string | null;\n}\n\nexport interface WorkspaceFileContent {\n workspaceKey: string;\n relativePath: string;\n content: string;\n fileType?: string;\n}\n\nexport interface WorkspaceFileListResult {\n data: WorkspaceFileItem[];\n total: number;\n}\n\nexport interface MiniProjectContext {\n kind: typeof MINI_CONTEXT_KIND;\n version: typeof MINI_CONTEXT_VERSION;\n appCode: string;\n workspaceKey: string;\n remotePrefix: string;\n tenantId?: string;\n endpoint?: string;\n profile?: string;\n updatedAt: string;\n}\n\nexport interface MiniFileSnapshot {\n localPath: string;\n remotePath: string;\n fileId?: string;\n sha256: string;\n size: number;\n lastModified?: number | string | null;\n}\n\nexport interface MiniPullManifest {\n version: typeof MINI_CONTEXT_VERSION;\n appCode: string;\n workspaceKey: string;\n remotePrefix: string;\n profile?: string;\n pulledAt: string;\n files: MiniFileSnapshot[];\n}\n\nexport interface MiniLocalFile {\n localPath: string;\n absolutePath: string;\n content: string;\n sha256: string;\n size: number;\n}\n\nexport type MiniLocalStatus = 'added' | 'modified' | 'deleted' | 'unchanged';\n\nexport interface MiniStatusEntry {\n localPath: string;\n remotePath: string;\n status: MiniLocalStatus;\n fileId?: string;\n sha256?: string;\n baseSha256?: string;\n}\n\nexport interface MiniValidationResult {\n valid: boolean;\n appCode?: string;\n workspaceKey?: string;\n errors: string[];\n warnings: string[];\n}\n\nexport interface MiniPullResult {\n appCode: string;\n workspaceKey: string;\n targetDir: string;\n files: MiniFileSnapshot[];\n}\n\nexport type MiniPushOperation = 'create' | 'update' | 'delete' | 'skip';\n\nexport interface MiniPushPlanItem {\n operation: MiniPushOperation;\n localPath: string;\n remotePath: string;\n fileId?: string;\n reason?: string;\n}\n\nexport interface MiniPushConflict {\n localPath: string;\n remotePath: string;\n reason: string;\n}\n\nexport interface MiniPushResult {\n appCode: string;\n workspaceKey: string;\n dryRun: boolean;\n operations: MiniPushPlanItem[];\n conflicts: MiniPushConflict[];\n}\n\nexport interface MiniPreviewTarget {\n key: 'bare' | 'standalone' | 'embedded';\n name: string;\n url: string;\n}\n\nexport type MiniSmokeTargetKind = 'preview' | 'manifest' | 'asset';\n\nexport interface MiniSmokeTargetResult {\n kind: MiniSmokeTargetKind;\n surface?: MiniPreviewTarget['key'];\n url: string;\n status: number | null;\n ok: boolean;\n error?: string;\n}\n\nexport interface MiniSmokeResult {\n ok: boolean;\n checked: number;\n passed: number;\n failed: number;\n targets: MiniSmokeTargetResult[];\n}\n\nexport type MiniVerifyNextAction =\n | 'push'\n | 'fix-validation'\n | 'resolve-conflict'\n | 'inspect-status'\n | 'inspect-preview';\n\nexport interface MiniVerifyPlan {\n operations: MiniPushPlanItem[];\n conflicts: MiniPushConflict[];\n}\n\nexport interface MiniVerifyReport {\n ok: boolean;\n validate: MiniValidationResult;\n status: MiniStatusEntry[];\n plan: MiniVerifyPlan | null;\n planSkipped?: string;\n preview: MiniPreviewTarget[];\n smoke?: MiniSmokeResult | null;\n}\n\nexport interface MiniVerifySummary {\n validate: {\n ok: boolean;\n errors: number;\n warnings: number;\n appCode?: string;\n workspaceKey?: string;\n };\n status: {\n total: number;\n changed: number;\n added: number;\n modified: number;\n deleted: number;\n unchanged: number;\n };\n dryRun: {\n ok: boolean;\n skipped: boolean;\n skippedReason?: string;\n operations: number;\n creates: number;\n updates: number;\n deletes: number;\n skips: number;\n conflicts: number;\n };\n preview: {\n ok: boolean;\n count: number;\n surfaces: MiniPreviewTarget['key'][];\n };\n}\n\nexport interface MiniVerifyChangedFile {\n path: string;\n status: MiniLocalStatus;\n remotePath: string;\n}\n\nexport interface MiniVerifyPreviewUrl {\n surface: MiniPreviewTarget['key'];\n name: string;\n url: string;\n}\n\nexport interface MiniVerifyBrowserAcceptance {\n recommendedUrl?: string;\n expectedTexts: string[];\n expectedNetwork: string[];\n console: {\n failOn: Array<'error' | 'warn'>;\n };\n}\n\nexport interface MiniVerifyAgentReport {\n ok: boolean;\n safeToPush: boolean;\n nextAction: MiniVerifyNextAction;\n summary: MiniVerifySummary;\n changedFiles: MiniVerifyChangedFile[];\n conflicts: MiniPushConflict[];\n previewUrls: MiniVerifyPreviewUrl[];\n browserAcceptance: MiniVerifyBrowserAcceptance;\n smoke: MiniSmokeResult | null;\n}\n","import type { AxiosInstance } from 'axios';\nimport type { WorkspaceFileContent, WorkspaceFileItem, WorkspaceFileListResult } from './types';\n\nconst MODEL = 'TenantExtensionWorkspaceFile';\n\nexport async function listWorkspaceFiles(\n http: AxiosInstance,\n workspaceKey: string,\n options: { pageSize?: number } = {},\n): Promise<WorkspaceFileListResult> {\n const resp = await http.get(`/api/models/${MODEL}`, {\n params: {\n state: 'workspace',\n filter: `workspaceKey:eq:${workspaceKey}`,\n page: 0,\n size: options.pageSize ?? 1000,\n order: 'relativePath:asc',\n },\n });\n const content = Array.isArray(resp.data?.content) ? resp.data.content : [];\n return {\n data: content.map(toWorkspaceFileItem),\n total: numberOr(resp.data?.totalElements, content.length),\n };\n}\n\nexport async function loadWorkspaceFileContent(\n http: AxiosInstance,\n fileId: string,\n): Promise<WorkspaceFileContent> {\n const resp = await http.get(`/api/models/${MODEL}/${encodeURIComponent(fileId)}/action/load-content`);\n return toWorkspaceFileContent(unwrapActionData(resp.data));\n}\n\nexport async function saveWorkspaceFileContent(\n http: AxiosInstance,\n fileId: string,\n content: string,\n): Promise<WorkspaceFileItem> {\n const resp = await http.post(\n `/api/models/${MODEL}/${encodeURIComponent(fileId)}/action/save-content`,\n { form: { content } },\n );\n return toWorkspaceFileItem(unwrapActionData(resp.data));\n}\n\nexport async function createWorkspaceFile(\n http: AxiosInstance,\n workspaceKey: string,\n relativePath: string,\n content: string,\n): Promise<WorkspaceFileItem> {\n const resp = await http.post(`/api/models/${MODEL}`, {\n workspaceKey,\n relativePath,\n content,\n });\n return toWorkspaceFileItem(resp.data);\n}\n\nexport async function deleteWorkspaceFile(http: AxiosInstance, fileId: string): Promise<void> {\n await http.delete(`/api/models/${MODEL}/${encodeURIComponent(fileId)}`);\n}\n\nfunction unwrapActionData(value: unknown): unknown {\n if (value && typeof value === 'object' && 'data' in value) {\n return (value as { data?: unknown }).data;\n }\n return value;\n}\n\nfunction toWorkspaceFileItem(value: unknown): WorkspaceFileItem {\n const obj = asRecord(value);\n return {\n id: stringOr(obj.id),\n workspaceKey: stringOr(obj.workspaceKey),\n relativePath: stringOr(obj.relativePath),\n fileType: stringOrUndefined(obj.fileType),\n fileSize: numberOr(obj.fileSize, 0),\n lastModified: primitiveOrNull(obj.lastModified),\n };\n}\n\nfunction toWorkspaceFileContent(value: unknown): WorkspaceFileContent {\n const obj = asRecord(value);\n return {\n workspaceKey: stringOr(obj.workspaceKey),\n relativePath: stringOr(obj.relativePath),\n content: stringOr(obj.content),\n fileType: stringOrUndefined(obj.fileType),\n };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};\n}\n\nfunction stringOr(value: unknown): string {\n return typeof value === 'string' ? value : value == null ? '' : String(value);\n}\n\nfunction stringOrUndefined(value: unknown): string | undefined {\n return value == null ? undefined : stringOr(value);\n}\n\nfunction numberOr(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n}\n\nfunction primitiveOrNull(value: unknown): number | string | null {\n if (typeof value === 'number' || typeof value === 'string') return value;\n return null;\n}\n","import * as crypto from 'node:crypto';\nimport type { Dirent } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { AxiosInstance } from 'axios';\nimport { CliError, CliErrorCode } from '../errors';\nimport {\n createWorkspaceFile,\n deleteWorkspaceFile,\n listWorkspaceFiles,\n loadWorkspaceFileContent,\n saveWorkspaceFileContent,\n} from './workspace-files';\nimport {\n MINI_CONTEXT_FILE,\n MINI_CONTEXT_KIND,\n MINI_CONTEXT_VERSION,\n MINI_PULL_MANIFEST_FILE,\n MINI_REMOTE_ROOT,\n MINI_STATE_DIR,\n type MiniFileSnapshot,\n type MiniPushOperation,\n type MiniLocalFile,\n type MiniPreviewTarget,\n type MiniProjectContext,\n type MiniPullManifest,\n type MiniPullResult,\n type MiniSmokeResult,\n type MiniSmokeTargetResult,\n type MiniVerifyAgentReport,\n type MiniVerifyBrowserAcceptance,\n type MiniVerifyChangedFile,\n type MiniVerifyNextAction,\n type MiniVerifyPreviewUrl,\n type MiniVerifyReport,\n type MiniPushConflict,\n type MiniPushPlanItem,\n type MiniPushResult,\n type MiniStatusEntry,\n type MiniSurface,\n type MiniTemplateKind,\n type MiniValidationResult,\n type WorkspaceFileContent,\n type WorkspaceFileItem,\n} from './types';\nimport {\n buildMiniAppTemplateFiles as buildMiniAppTemplateFileSet,\n defaultMiniRequiredScopes,\n} from './templates';\n\nconst MINI_APP_CODE_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst SCOPE_PATTERN = /^[A-Z][A-Za-z0-9_]*\\.[a-z][A-Za-z0-9]*$/;\nconst SEMVER_PATTERN =\n /^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$/;\nconst TOP_LEVEL_MANIFEST_KEYS = new Set([\n 'schemaVersion',\n 'code',\n 'name',\n 'version',\n 'sdkVersion',\n 'surfaces',\n 'defaultSurface',\n 'entry',\n 'bundleObjectRef',\n 'requiredScopes',\n]);\nconst ENTRY_KEYS = new Set(['html', 'css', 'js']);\nconst EXCLUDED_DIRS = new Set(['.git', MINI_STATE_DIR, 'node_modules', 'dist']);\nconst MINI_SDK_SCOPE_METHODS: Record<string, string> = {\n query: 'list',\n get: 'get',\n create: 'create',\n update: 'update',\n remove: 'delete',\n};\nconst MINI_MODEL_CONST_PATTERN = /\\bconst\\s+([A-Z][A-Za-z0-9_]*)\\s*=\\s*['\"]([A-Z][A-Za-z0-9_]*)['\"]\\s*;/g;\nconst MINI_SDK_MODEL_CALL_PATTERN =\n /\\bhcm\\.(query|get|create|update|remove)\\s*\\(\\s*\\{[\\s\\S]*?\\bmodel\\s*:\\s*(?:['\"]([A-Z][A-Za-z0-9_]*)['\"]|([A-Z][A-Za-z0-9_]*))/g;\nconst MINI_SDK_ACTION_CALL_PATTERN =\n /\\bhcm\\.action\\s*\\(\\s*\\{[\\s\\S]*?\\bmodel\\s*:\\s*(?:['\"]([A-Z][A-Za-z0-9_]*)['\"]|([A-Z][A-Za-z0-9_]*))[\\s\\S]*?\\baction\\s*:\\s*['\"]([a-z][A-Za-z0-9]*)['\"]/g;\n\nexport interface MiniInitArgs {\n appCode: string;\n workspaceKey: string;\n targetDir: string;\n name?: string;\n defaultSurface?: MiniSurface;\n templateKind?: MiniTemplateKind;\n writableModel?: string;\n writableFields?: string[] | null;\n writableDefaults?: Record<string, unknown>;\n writableTodayDefaultFields?: string[];\n requiredScopes?: string[];\n tenantId?: string;\n endpoint?: string;\n profile?: string;\n force?: boolean;\n}\n\nexport interface MiniPullArgs {\n http: AxiosInstance;\n appCode: string;\n workspaceKey: string;\n targetDir: string;\n tenantId?: string;\n endpoint?: string;\n profile?: string;\n force?: boolean;\n}\n\nexport interface MiniPushArgs {\n http: AxiosInstance;\n rootDir: string;\n dryRun?: boolean;\n}\n\nexport async function initMiniAppProject(args: MiniInitArgs): Promise<MiniPullResult> {\n const appCode = normalizeAppCode(args.appCode);\n const workspaceKey = normalizeWorkspaceKey(args.workspaceKey);\n const targetDir = path.resolve(args.targetDir);\n const remotePrefix = miniRemotePrefix(appCode);\n const templateKind = args.templateKind ?? 'dashboard';\n const files = buildMiniAppTemplateFiles({\n appCode,\n name: args.name,\n defaultSurface: args.defaultSurface ?? 'embedded',\n templateKind,\n writableModel: args.writableModel,\n writableFields: args.writableFields,\n writableDefaults: args.writableDefaults,\n writableTodayDefaultFields: args.writableTodayDefaultFields,\n requiredScopes: args.requiredScopes?.length\n ? args.requiredScopes\n : defaultMiniRequiredScopes(templateKind, { model: args.writableModel, fields: args.writableFields }),\n tenantId: args.tenantId ?? '1',\n });\n\n await ensureTargetWritable(\n targetDir,\n files.map((file) => file.localPath).concat([MINI_CONTEXT_FILE]),\n !!args.force,\n );\n await fs.mkdir(targetDir, { recursive: true });\n for (const file of files) {\n await writeText(path.join(targetDir, file.localPath), file.content);\n }\n\n const context: MiniProjectContext = {\n kind: MINI_CONTEXT_KIND,\n version: MINI_CONTEXT_VERSION,\n appCode,\n workspaceKey,\n remotePrefix,\n tenantId: args.tenantId,\n endpoint: args.endpoint,\n profile: args.profile,\n updatedAt: new Date().toISOString(),\n };\n await writeJson(path.join(targetDir, MINI_CONTEXT_FILE), context);\n\n const localFiles = await readLocalMiniFiles(targetDir);\n await writeMiniState(targetDir, context, [], new Map());\n return {\n appCode,\n workspaceKey,\n targetDir,\n files: localFiles.map((file) => toSnapshot(context, file)),\n };\n}\n\nexport async function pullMiniAppProject(args: MiniPullArgs): Promise<MiniPullResult> {\n const appCode = normalizeAppCode(args.appCode);\n const workspaceKey = normalizeWorkspaceKey(args.workspaceKey);\n const targetDir = path.resolve(args.targetDir);\n const remotePrefix = miniRemotePrefix(appCode);\n const remoteFiles = await loadRemoteMiniAppFiles(args.http, workspaceKey, appCode);\n if (!remoteFiles.length) {\n throw new CliError({\n code: CliErrorCode.BUSINESS_RULE_VIOLATION,\n message: `mini app '${appCode}' not found in workspace '${workspaceKey}'`,\n });\n }\n\n const pullFiles = remoteFiles\n .filter((file) => !isDirectoryMarkerPath(file.item.relativePath))\n .map((file) => ({\n localPath: localPathFromRemote(remotePrefix, file.item.relativePath),\n content: file.content.content,\n remote: file.item,\n }));\n await ensureTargetWritable(\n targetDir,\n pullFiles.map((file) => file.localPath).concat([MINI_CONTEXT_FILE]),\n !!args.force,\n );\n await fs.mkdir(targetDir, { recursive: true });\n for (const file of pullFiles) {\n await writeText(path.join(targetDir, file.localPath), file.content);\n }\n\n const context: MiniProjectContext = {\n kind: MINI_CONTEXT_KIND,\n version: MINI_CONTEXT_VERSION,\n appCode,\n workspaceKey,\n remotePrefix,\n tenantId: args.tenantId,\n endpoint: args.endpoint,\n profile: args.profile,\n updatedAt: new Date().toISOString(),\n };\n await writeJson(path.join(targetDir, MINI_CONTEXT_FILE), context);\n\n const localFiles = await readLocalMiniFiles(targetDir);\n const remoteByLocalPath = new Map(\n pullFiles.map((file) => [file.localPath, file.remote] as const),\n );\n await writeMiniState(targetDir, context, localFiles, remoteByLocalPath);\n const manifest = await readPullManifest(targetDir);\n return {\n appCode,\n workspaceKey,\n targetDir,\n files: manifest.files,\n };\n}\n\nexport async function getMiniStatus(rootDir: string): Promise<MiniStatusEntry[]> {\n const root = path.resolve(rootDir);\n const context = await readMiniContext(root);\n const manifest = await readPullManifest(root);\n const localFiles = await readLocalMiniFiles(root);\n return computeStatusEntries(context, manifest, localFiles);\n}\n\nexport async function validateMiniProject(rootDir: string): Promise<MiniValidationResult> {\n const root = path.resolve(rootDir);\n const errors: string[] = [];\n const warnings: string[] = [];\n let context: MiniProjectContext | null = null;\n\n try {\n context = await readMiniContext(root);\n } catch (e) {\n errors.push((e as Error).message);\n }\n\n const manifestPath = path.join(root, 'manifest.json');\n let manifest: Record<string, unknown> | null = null;\n try {\n manifest = JSON.parse(await fs.readFile(manifestPath, 'utf-8')) as Record<string, unknown>;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n errors.push('manifest.json is required');\n } else {\n errors.push(`manifest.json is not valid JSON: ${(e as Error).message}`);\n }\n }\n\n if (context) {\n if (!MINI_APP_CODE_PATTERN.test(context.appCode)) {\n errors.push('appCode must match ^[a-z][a-z0-9-]*$');\n }\n if (context.remotePrefix !== miniRemotePrefix(context.appCode)) {\n errors.push(`context remotePrefix must be ${miniRemotePrefix(context.appCode)}`);\n }\n }\n\n if (manifest) {\n await validateManifestObject(manifest, context, root, errors, warnings);\n }\n\n return {\n valid: errors.length === 0,\n appCode: context?.appCode,\n workspaceKey: context?.workspaceKey,\n errors,\n warnings,\n };\n}\n\nexport async function pushMiniAppProject(args: MiniPushArgs): Promise<MiniPushResult> {\n const root = path.resolve(args.rootDir);\n const validation = await validateMiniProject(root);\n if (!validation.valid) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: `mini project validation failed: ${validation.errors.join('; ')}`,\n });\n }\n\n const context = await readMiniContext(root);\n const manifest = await readPullManifest(root);\n const localFiles = await readLocalMiniFiles(root);\n const remoteFiles = await loadRemoteMiniAppFiles(args.http, context.workspaceKey, context.appCode);\n const plan = buildPushPlan(context, manifest, localFiles, remoteFiles);\n\n const result: MiniPushResult = {\n appCode: context.appCode,\n workspaceKey: context.workspaceKey,\n dryRun: !!args.dryRun,\n operations: plan.operations,\n conflicts: plan.conflicts,\n };\n if (plan.conflicts.length || args.dryRun) {\n return result;\n }\n\n const localByPath = new Map(localFiles.map((file) => [file.localPath, file] as const));\n for (const operation of plan.operations) {\n if (operation.operation === 'skip') continue;\n if (operation.operation === 'delete') {\n if (operation.fileId) await deleteWorkspaceFile(args.http, operation.fileId);\n continue;\n }\n const localFile = localByPath.get(operation.localPath);\n if (!localFile) continue;\n if (operation.operation === 'create') {\n await createWorkspaceFile(\n args.http,\n context.workspaceKey,\n operation.remotePath,\n localFile.content,\n );\n }\n if (operation.operation === 'update' && operation.fileId) {\n await saveWorkspaceFileContent(args.http, operation.fileId, localFile.content);\n }\n }\n\n const latestRemoteFiles = await listWorkspaceFiles(args.http, context.workspaceKey, {\n pageSize: 5000,\n });\n const remoteByLocalPath = new Map<string, WorkspaceFileItem>();\n for (const remote of latestRemoteFiles.data) {\n if (!remote.relativePath.startsWith(context.remotePrefix + '/')) continue;\n remoteByLocalPath.set(localPathFromRemote(context.remotePrefix, remote.relativePath), remote);\n }\n await writeMiniState(root, context, localFiles, remoteByLocalPath);\n return result;\n}\n\nexport async function buildMiniPreviewTargets(\n rootDir: string,\n frontendBaseUrl: string,\n): Promise<MiniPreviewTarget[]> {\n const context = await readMiniContext(path.resolve(rootDir));\n const app = encodeURIComponent(context.appCode);\n const workspace = encodeURIComponent(context.workspaceKey);\n const base = frontendBaseUrl.replace(/\\/+$/, '');\n return [\n {\n key: 'bare',\n name: 'bare runtime',\n url: `${base}/mini.html?app=${app}&workspaceKey=${workspace}`,\n },\n {\n key: 'standalone',\n name: 'standalone page',\n url: `${base}/mini/${app}?workspaceKey=${workspace}`,\n },\n {\n key: 'embedded',\n name: 'embedded page',\n url: `${base}/custom/${app}?workspaceKey=${workspace}`,\n },\n ];\n}\n\nexport interface MiniSmokeFetchResponse {\n status: number;\n}\n\nexport type MiniSmokeFetch = (\n url: string,\n init?: { headers?: Record<string, string>; method: 'GET'; signal?: AbortSignal },\n) => Promise<MiniSmokeFetchResponse>;\n\nexport interface MiniSmokeCheckArgs {\n rootDir: string;\n frontendBaseUrl: string;\n previewTargets: MiniPreviewTarget[];\n fetcher?: MiniSmokeFetch;\n accessToken?: string | null;\n timeoutMs?: number;\n}\n\nexport async function runMiniSmokeChecks(args: MiniSmokeCheckArgs): Promise<MiniSmokeResult> {\n const context = await readMiniContext(path.resolve(args.rootDir));\n const targets = buildMiniSmokeTargets(context, args.frontendBaseUrl, args.previewTargets);\n const results = await Promise.all(\n targets.map((target) =>\n checkMiniSmokeTarget(\n target,\n args.fetcher ?? defaultSmokeFetch,\n args.timeoutMs ?? 5000,\n args.accessToken,\n context.workspaceKey,\n ),\n ),\n );\n const passed = results.filter((target) => target.ok).length;\n const failed = results.length - passed;\n return {\n ok: failed === 0,\n checked: results.length,\n passed,\n failed,\n targets: results,\n };\n}\n\nfunction buildMiniSmokeTargets(\n context: MiniProjectContext,\n frontendBaseUrl: string,\n previewTargets: MiniPreviewTarget[],\n): MiniSmokeTargetResult[] {\n const base = frontendBaseUrl.replace(/\\/+$/, '');\n const app = encodeURIComponent(context.appCode);\n return [\n ...previewTargets.map(\n (target): MiniSmokeTargetResult => ({\n kind: 'preview',\n surface: target.key,\n url: target.url,\n status: null,\n ok: false,\n }),\n ),\n {\n kind: 'manifest',\n url: `${base}/api/mini-apps/${app}`,\n status: null,\n ok: false,\n },\n ...['index.html', 'index.css', 'index.js'].map(\n (file): MiniSmokeTargetResult => ({\n kind: 'asset',\n url: `${base}/static/mini-apps/${app}/${file}`,\n status: null,\n ok: false,\n }),\n ),\n ];\n}\n\nasync function checkMiniSmokeTarget(\n target: MiniSmokeTargetResult,\n fetcher: MiniSmokeFetch,\n timeoutMs: number,\n accessToken?: string | null,\n workspaceKey?: string | null,\n): Promise<MiniSmokeTargetResult> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const headers =\n target.kind !== 'preview'\n ? {\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...(workspaceKey ? { 'X-Workspace-Key': workspaceKey } : {}),\n }\n : undefined;\n const response = await fetcher(target.url, {\n ...(headers ? { headers } : {}),\n method: 'GET',\n signal: controller.signal,\n });\n const ok = response.status >= 200 && response.status < 400;\n return {\n ...target,\n status: response.status,\n ok,\n error: ok ? undefined : `HTTP ${response.status}`,\n };\n } catch (e) {\n return {\n ...target,\n status: null,\n ok: false,\n error: (e as Error).message || 'request failed',\n };\n } finally {\n clearTimeout(timeout);\n }\n}\n\nasync function defaultSmokeFetch(\n url: string,\n init?: { headers?: Record<string, string>; method: 'GET'; signal?: AbortSignal },\n): Promise<MiniSmokeFetchResponse> {\n if (typeof fetch !== 'function') throw new Error('fetch is not available');\n return fetch(url, init);\n}\n\nexport function buildMiniVerifyAgentReport(report: MiniVerifyReport): MiniVerifyAgentReport {\n const statusCounts = countStatuses(report.status);\n const operationCounts = countOperations(report.plan?.operations ?? []);\n const conflicts = report.plan?.conflicts ?? [];\n const dryRunOk = !!report.plan && conflicts.length === 0;\n const smoke = report.smoke ?? null;\n const smokeOk = !smoke || smoke.ok;\n const previewUrls = report.preview.map(\n (target): MiniVerifyPreviewUrl => ({\n surface: target.key,\n name: target.name,\n url: target.url,\n }),\n );\n const safeToPush = report.validate.valid && dryRunOk && previewUrls.length > 0 && smokeOk;\n const nextAction = chooseMiniVerifyNextAction(report, previewUrls, smoke);\n\n return {\n ok: report.ok && smokeOk,\n safeToPush,\n nextAction,\n summary: {\n validate: {\n ok: report.validate.valid,\n errors: report.validate.errors.length,\n warnings: report.validate.warnings.length,\n appCode: report.validate.appCode,\n workspaceKey: report.validate.workspaceKey,\n },\n status: {\n total: report.status.length,\n changed: statusCounts.added + statusCounts.modified + statusCounts.deleted,\n added: statusCounts.added,\n modified: statusCounts.modified,\n deleted: statusCounts.deleted,\n unchanged: statusCounts.unchanged,\n },\n dryRun: {\n ok: dryRunOk,\n skipped: !report.plan,\n skippedReason: report.plan ? undefined : report.planSkipped,\n operations: operationCounts.create + operationCounts.update + operationCounts.delete,\n creates: operationCounts.create,\n updates: operationCounts.update,\n deletes: operationCounts.delete,\n skips: operationCounts.skip,\n conflicts: conflicts.length,\n },\n preview: {\n ok: previewUrls.length > 0,\n count: previewUrls.length,\n surfaces: previewUrls.map((target) => target.surface),\n },\n },\n changedFiles: report.status\n .filter((entry) => entry.status !== 'unchanged')\n .map(\n (entry): MiniVerifyChangedFile => ({\n path: entry.localPath,\n status: entry.status,\n remotePath: entry.remotePath,\n }),\n ),\n conflicts,\n previewUrls,\n browserAcceptance: buildMiniVerifyBrowserAcceptance(report, previewUrls),\n smoke,\n };\n}\n\nfunction buildMiniVerifyBrowserAcceptance(\n report: MiniVerifyReport,\n previewUrls: MiniVerifyPreviewUrl[],\n): MiniVerifyBrowserAcceptance {\n const recommendedUrl = preferredBrowserPreview(previewUrls)?.url;\n const appCode = report.validate.appCode;\n return {\n recommendedUrl,\n expectedTexts: appCode ? [appCode] : [],\n expectedNetwork: expectedBrowserNetwork(report, recommendedUrl),\n console: {\n failOn: ['error', 'warn'],\n },\n };\n}\n\nfunction preferredBrowserPreview(\n previewUrls: MiniVerifyPreviewUrl[],\n): MiniVerifyPreviewUrl | undefined {\n return (\n previewUrls.find((target) => target.surface === 'standalone') ??\n previewUrls.find((target) => target.surface === 'bare') ??\n previewUrls[0]\n );\n}\n\nfunction expectedBrowserNetwork(report: MiniVerifyReport, recommendedUrl?: string): string[] {\n const smokeUrls = report.smoke?.targets\n .filter((target) => target.kind !== 'preview')\n .map((target) => target.url);\n if (smokeUrls?.length) return Array.from(new Set(smokeUrls));\n\n const appCode = report.validate.appCode;\n if (!recommendedUrl || !appCode) return [];\n try {\n const origin = new URL(recommendedUrl).origin;\n return [\n `${origin}/api/mini-apps/${appCode}`,\n `${origin}/static/mini-apps/${appCode}/index.html`,\n `${origin}/static/mini-apps/${appCode}/index.css`,\n `${origin}/static/mini-apps/${appCode}/index.js`,\n ];\n } catch {\n return [];\n }\n}\n\nfunction chooseMiniVerifyNextAction(\n report: MiniVerifyReport,\n previewUrls: MiniVerifyPreviewUrl[],\n smoke: MiniSmokeResult | null,\n): MiniVerifyNextAction {\n if (smoke && !smoke.ok) return 'inspect-preview';\n if (!report.validate.valid) return 'fix-validation';\n if (!report.plan) return 'inspect-status';\n if (report.plan.conflicts.length) return 'resolve-conflict';\n if (!previewUrls.length) return 'inspect-preview';\n return 'push';\n}\n\nexport function formatMiniVerifyReport(report: MiniVerifyReport): string {\n const totalSteps = report.smoke ? 5 : 4;\n const lines: string[] = [];\n lines.push(`verify: ${report.ok ? 'OK' : 'FAIL'}`);\n lines.push('');\n\n // 1. validate\n lines.push(`[1/${totalSteps}] validate: ${report.validate.valid ? '✓ pass' : '✗ fail'}`);\n if (report.validate.errors.length) {\n for (const err of report.validate.errors) lines.push(` error: ${err}`);\n }\n if (report.validate.warnings.length) {\n for (const warn of report.validate.warnings) lines.push(` warn: ${warn}`);\n }\n\n // 2. status\n const statusBucket = { added: 0, modified: 0, deleted: 0, unchanged: 0 };\n for (const row of report.status) statusBucket[row.status] += 1;\n lines.push(\n `[2/${totalSteps}] status: ${statusBucket.added} added, ${statusBucket.modified} modified, ${statusBucket.deleted} deleted, ${statusBucket.unchanged} unchanged`,\n );\n for (const row of report.status) {\n if (row.status === 'unchanged') continue;\n lines.push(` ${row.status.padEnd(9)} ${row.localPath}`);\n }\n\n // 3. push dry-run\n if (report.plan) {\n if (report.plan.conflicts.length) {\n lines.push(`[3/${totalSteps}] dry-run: ✗ ${report.plan.conflicts.length} conflict(s)`);\n for (const c of report.plan.conflicts) lines.push(` ${c.localPath}: ${c.reason}`);\n } else {\n const opCount = report.plan.operations.filter((op) => op.operation !== 'skip').length;\n lines.push(`[3/${totalSteps}] dry-run: ✓ ${opCount} operation(s) planned (no remote writes)`);\n for (const op of report.plan.operations) {\n if (op.operation === 'skip') continue;\n lines.push(` ${op.operation.padEnd(7)} ${op.localPath}`);\n }\n }\n } else {\n lines.push(`[3/${totalSteps}] dry-run: - skipped (${report.planSkipped ?? 'unknown reason'})`);\n }\n\n // 4. preview URLs\n if (report.preview.length) {\n lines.push(`[4/${totalSteps}] preview:`);\n for (const target of report.preview) lines.push(` ${target.key.padEnd(11)} ${target.url}`);\n } else {\n lines.push(`[4/${totalSteps}] preview: - (no targets)`);\n }\n\n // 5. smoke HTTP checks\n if (report.smoke) {\n const marker = report.smoke.ok ? '✓' : '✗';\n lines.push(\n `[5/5] smoke: ${marker} ${report.smoke.passed}/${report.smoke.checked} passed`,\n );\n for (const target of report.smoke.targets) {\n const targetMarker = target.ok ? '✓' : '✗';\n const label = target.surface ? `${target.kind}:${target.surface}` : target.kind;\n const status = target.status ?? '-';\n const error = target.error ? ` (${target.error})` : '';\n lines.push(` ${targetMarker} ${label.padEnd(18)} ${status} ${target.url}${error}`);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction countStatuses(status: MiniStatusEntry[]): Record<MiniStatusEntry['status'], number> {\n const counts: Record<MiniStatusEntry['status'], number> = {\n added: 0,\n modified: 0,\n deleted: 0,\n unchanged: 0,\n };\n for (const entry of status) counts[entry.status] += 1;\n return counts;\n}\n\nfunction countOperations(operations: MiniPushPlanItem[]): Record<MiniPushOperation, number> {\n const counts: Record<MiniPushOperation, number> = {\n create: 0,\n update: 0,\n delete: 0,\n skip: 0,\n };\n for (const operation of operations) counts[operation.operation] += 1;\n return counts;\n}\n\nexport async function readMiniContext(rootDir: string): Promise<MiniProjectContext> {\n const file = path.join(rootDir, MINI_CONTEXT_FILE);\n let parsed: unknown;\n try {\n parsed = JSON.parse(await fs.readFile(file, 'utf-8'));\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new CliError({\n code: CliErrorCode.CONFIG_PARSE_ERROR,\n message: `${MINI_CONTEXT_FILE} not found; run hcm workspace add mini <appCode> or hcm workspace pull first`,\n });\n }\n throw new CliError({\n code: CliErrorCode.CONFIG_PARSE_ERROR,\n message: `${MINI_CONTEXT_FILE} is invalid: ${(e as Error).message}`,\n });\n }\n const obj = asRecord(parsed);\n if (obj.kind !== MINI_CONTEXT_KIND || obj.version !== MINI_CONTEXT_VERSION) {\n throw new CliError({\n code: CliErrorCode.CONFIG_PARSE_ERROR,\n message: `${MINI_CONTEXT_FILE} is not an HCM mini app context`,\n });\n }\n return {\n kind: MINI_CONTEXT_KIND,\n version: MINI_CONTEXT_VERSION,\n appCode: normalizeAppCode(stringRequired(obj.appCode, 'appCode')),\n workspaceKey: normalizeWorkspaceKey(stringRequired(obj.workspaceKey, 'workspaceKey')),\n remotePrefix: stringRequired(obj.remotePrefix, 'remotePrefix'),\n tenantId: stringOptional(obj.tenantId),\n endpoint: stringOptional(obj.endpoint),\n profile: stringOptional(obj.profile),\n updatedAt: stringOptional(obj.updatedAt) ?? new Date(0).toISOString(),\n };\n}\n\nexport async function readPullManifest(rootDir: string): Promise<MiniPullManifest> {\n const file = path.join(rootDir, MINI_STATE_DIR, MINI_PULL_MANIFEST_FILE);\n try {\n const parsed = JSON.parse(await fs.readFile(file, 'utf-8')) as MiniPullManifest;\n return {\n version: MINI_CONTEXT_VERSION,\n appCode: parsed.appCode ? normalizeAppCode(parsed.appCode) : '',\n workspaceKey: parsed.workspaceKey ? normalizeWorkspaceKey(parsed.workspaceKey) : '',\n remotePrefix: parsed.remotePrefix ?? '',\n profile: parsed.profile,\n pulledAt: parsed.pulledAt,\n files: Array.isArray(parsed.files) ? parsed.files : [],\n };\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n return {\n version: MINI_CONTEXT_VERSION,\n appCode: '',\n workspaceKey: '',\n remotePrefix: '',\n pulledAt: new Date(0).toISOString(),\n files: [],\n };\n }\n throw e;\n }\n}\n\nasync function validateManifestObject(\n manifest: Record<string, unknown>,\n context: MiniProjectContext | null,\n root: string,\n errors: string[],\n warnings: string[],\n): Promise<void> {\n for (const key of Object.keys(manifest)) {\n if (!TOP_LEVEL_MANIFEST_KEYS.has(key)) errors.push(`manifest has unsupported key: ${key}`);\n }\n requireString(manifest, 'schemaVersion', errors, (value) => value === '1.0');\n requireString(manifest, 'code', errors, (value) => /^[a-zA-Z0-9-]+$/.test(value));\n requireString(manifest, 'name', errors, (value) => value.trim().length > 0);\n requireString(manifest, 'version', errors, (value) => SEMVER_PATTERN.test(value));\n requireString(manifest, 'sdkVersion', errors, (value) => SEMVER_PATTERN.test(value));\n\n const code = typeof manifest.code === 'string' ? manifest.code : '';\n if (context && code && code !== context.appCode) {\n errors.push(`manifest code must equal context appCode: ${context.appCode}`);\n }\n const requiredScopes = asStringArray(manifest.requiredScopes);\n if (!requiredScopes.length) {\n errors.push('manifest requiredScopes must contain at least one scope');\n }\n for (const scope of requiredScopes) {\n if (!SCOPE_PATTERN.test(scope)) {\n errors.push(`manifest requiredScopes has invalid scope: ${scope}`);\n }\n }\n\n const surfaces = asStringArray(manifest.surfaces);\n const defaultSurface = typeof manifest.defaultSurface === 'string' ? manifest.defaultSurface : '';\n if (surfaces.length) {\n for (const surface of surfaces) {\n if (surface !== 'embedded' && surface !== 'standalone') {\n errors.push(`manifest surfaces has invalid value: ${surface}`);\n }\n }\n if (new Set(surfaces).size !== surfaces.length) {\n errors.push('manifest surfaces must be unique');\n }\n } else {\n warnings.push('manifest surfaces is omitted; backend default may apply');\n }\n if (defaultSurface && surfaces.length && !surfaces.includes(defaultSurface)) {\n errors.push('manifest defaultSurface must be declared in surfaces');\n }\n\n const entry = asRecord(manifest.entry);\n if (!Object.keys(entry).length) {\n errors.push('manifest entry is required');\n }\n for (const key of Object.keys(entry)) {\n if (!ENTRY_KEYS.has(key)) errors.push(`manifest entry has unsupported key: ${key}`);\n }\n const html = stringOptional(entry.html);\n const js = stringOptional(entry.js);\n const css = stringOptional(entry.css);\n if (!html && !js) {\n errors.push('manifest entry must define html or js');\n }\n await validateEntryPath(root, 'entry.html', html, '.html', errors);\n await validateEntryPath(root, 'entry.js', js, '.js', errors);\n await validateEntryPath(root, 'entry.css', css, '.css', errors);\n await validateEntryRequiredScopes(root, js, requiredScopes, warnings);\n\n const bundleObjectRef = asRecord(manifest.bundleObjectRef);\n const pathSpec = asRecord(bundleObjectRef.pathSpec);\n const pathSegment = stringOptional(pathSpec.pathSegment);\n if (context && pathSegment && pathSegment !== miniRemotePrefix(context.appCode)) {\n errors.push(\n `manifest bundleObjectRef.pathSpec.pathSegment must be ${miniRemotePrefix(context.appCode)}`,\n );\n }\n}\n\nasync function validateEntryPath(\n root: string,\n field: string,\n value: string | undefined,\n suffix: string,\n errors: string[],\n): Promise<void> {\n if (!value) return;\n if (!value.endsWith(suffix)) errors.push(`${field} must end with ${suffix}`);\n try {\n normalizeRelativePath(value);\n } catch (e) {\n errors.push(`${field} is invalid: ${(e as Error).message}`);\n return;\n }\n try {\n await fs.access(path.join(root, value));\n } catch {\n errors.push(`${field} target file does not exist: ${value}`);\n }\n}\n\nasync function validateEntryRequiredScopes(\n root: string,\n jsPath: string | undefined,\n requiredScopes: string[],\n warnings: string[],\n): Promise<void> {\n if (!jsPath) return;\n const absolutePath = path.resolve(root, jsPath);\n if (!absolutePath.startsWith(root + path.sep)) return;\n let source = '';\n try {\n source = await fs.readFile(absolutePath, 'utf-8');\n } catch {\n return;\n }\n\n const declaredScopes = new Set(requiredScopes);\n for (const scope of inferMiniSdkRequiredScopes(source)) {\n if (!declaredScopes.has(scope)) {\n warnings.push(`manifest requiredScopes may be missing ${scope} for ${jsPath} SDK usage`);\n }\n }\n}\n\nfunction inferMiniSdkRequiredScopes(source: string): string[] {\n const constants = readMiniModelConstants(source);\n const scopes = new Set<string>();\n let match: RegExpExecArray | null;\n\n MINI_SDK_MODEL_CALL_PATTERN.lastIndex = 0;\n while ((match = MINI_SDK_MODEL_CALL_PATTERN.exec(source))) {\n const method = match[1];\n if (!method) continue;\n const model = match[2] ?? constants.get(match[3] ?? '');\n const action = MINI_SDK_SCOPE_METHODS[method];\n if (model && action) scopes.add(`${model}.${action}`);\n }\n\n MINI_SDK_ACTION_CALL_PATTERN.lastIndex = 0;\n while ((match = MINI_SDK_ACTION_CALL_PATTERN.exec(source))) {\n const model = match[1] ?? constants.get(match[2] ?? '');\n const action = match[3];\n if (model && action) scopes.add(`${model}.${action}`);\n }\n\n return Array.from(scopes);\n}\n\nfunction readMiniModelConstants(source: string): Map<string, string> {\n const constants = new Map<string, string>();\n let match: RegExpExecArray | null;\n MINI_MODEL_CONST_PATTERN.lastIndex = 0;\n while ((match = MINI_MODEL_CONST_PATTERN.exec(source))) {\n if (!match[1] || !match[2]) continue;\n constants.set(match[1], match[2]);\n }\n return constants;\n}\n\nfunction buildPushPlan(\n context: MiniProjectContext,\n manifest: MiniPullManifest,\n localFiles: MiniLocalFile[],\n remoteFiles: Array<{ item: WorkspaceFileItem; content: WorkspaceFileContent }>,\n): { operations: MiniPushPlanItem[]; conflicts: MiniPushConflict[] } {\n const status = computeStatusEntries(context, manifest, localFiles);\n const baseByLocal = new Map(manifest.files.map((file) => [file.localPath, file] as const));\n const remoteByLocal = new Map(\n remoteFiles.map((file) => [\n localPathFromRemote(context.remotePrefix, file.item.relativePath),\n file,\n ] as const),\n );\n const operations: MiniPushPlanItem[] = [];\n const conflicts: MiniPushConflict[] = [];\n\n for (const entry of status) {\n const remote = remoteByLocal.get(entry.localPath);\n const base = baseByLocal.get(entry.localPath);\n const remoteSha = remote ? sha256(remote.content.content) : undefined;\n if (entry.status === 'added') {\n if (remote) {\n conflicts.push(conflict(entry, 'remote file already exists but was not in local snapshot'));\n } else {\n operations.push({ operation: 'create', localPath: entry.localPath, remotePath: entry.remotePath });\n }\n continue;\n }\n if (entry.status === 'modified') {\n if (!remote) {\n conflicts.push(conflict(entry, 'remote file was deleted after last pull'));\n } else if (base && remoteSha !== base.sha256) {\n conflicts.push(conflict(entry, 'remote file changed after last pull'));\n } else {\n operations.push({\n operation: 'update',\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n fileId: remote.item.id,\n });\n }\n continue;\n }\n if (entry.status === 'deleted') {\n if (!remote) {\n operations.push({\n operation: 'skip',\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n reason: 'already deleted remotely',\n });\n } else if (base && remoteSha !== base.sha256) {\n conflicts.push(conflict(entry, 'remote file changed after last pull'));\n } else {\n operations.push({\n operation: 'delete',\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n fileId: remote.item.id,\n });\n }\n continue;\n }\n operations.push({\n operation: 'skip',\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n fileId: remote?.item.id,\n reason: 'unchanged',\n });\n }\n return { operations, conflicts };\n}\n\nfunction computeStatusEntries(\n context: MiniProjectContext,\n manifest: MiniPullManifest,\n localFiles: MiniLocalFile[],\n): MiniStatusEntry[] {\n const baseByLocal = new Map(manifest.files.map((file) => [file.localPath, file] as const));\n const localByPath = new Map(localFiles.map((file) => [file.localPath, file] as const));\n const paths = [...new Set([...baseByLocal.keys(), ...localByPath.keys()])].sort();\n return paths.map((localPath) => {\n const base = baseByLocal.get(localPath);\n const local = localByPath.get(localPath);\n const remotePath = base?.remotePath ?? remotePathFromLocal(context.appCode, localPath);\n if (!base && local) {\n return { localPath, remotePath, status: 'added', sha256: local.sha256 };\n }\n if (base && !local) {\n return {\n localPath,\n remotePath,\n status: 'deleted',\n fileId: base.fileId,\n baseSha256: base.sha256,\n };\n }\n if (base && local && base.sha256 !== local.sha256) {\n return {\n localPath,\n remotePath,\n status: 'modified',\n fileId: base.fileId,\n sha256: local.sha256,\n baseSha256: base.sha256,\n };\n }\n return {\n localPath,\n remotePath,\n status: 'unchanged',\n fileId: base?.fileId,\n sha256: local?.sha256,\n baseSha256: base?.sha256,\n };\n });\n}\n\nasync function loadRemoteMiniAppFiles(\n http: AxiosInstance,\n workspaceKey: string,\n appCode: string,\n): Promise<Array<{ item: WorkspaceFileItem; content: WorkspaceFileContent }>> {\n const prefix = miniRemotePrefix(appCode) + '/';\n const list = await listWorkspaceFiles(http, workspaceKey, { pageSize: 5000 });\n const result: Array<{ item: WorkspaceFileItem; content: WorkspaceFileContent }> = [];\n for (const item of list.data) {\n if (!item.relativePath.startsWith(prefix) || isDirectoryMarkerPath(item.relativePath)) continue;\n const content = await loadWorkspaceFileContent(http, item.id);\n result.push({ item, content });\n }\n return result;\n}\n\nasync function readLocalMiniFiles(root: string): Promise<MiniLocalFile[]> {\n const files: MiniLocalFile[] = [];\n await walk(root, '', files);\n files.sort((a, b) => a.localPath.localeCompare(b.localPath));\n return files;\n}\n\nasync function walk(root: string, relativeDir: string, files: MiniLocalFile[]): Promise<void> {\n const absoluteDir = path.join(root, relativeDir);\n let entries: Dirent[];\n try {\n entries = await fs.readdir(absoluteDir, { withFileTypes: true });\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw e;\n }\n for (const entry of entries) {\n if (entry.name === MINI_CONTEXT_FILE) continue;\n if (entry.isDirectory() && EXCLUDED_DIRS.has(entry.name)) continue;\n const localPath = normalizeRelativePath(path.posix.join(toPosix(relativeDir), entry.name));\n const absolutePath = path.join(root, localPath);\n if (entry.isDirectory()) {\n await walk(root, localPath, files);\n } else if (entry.isFile()) {\n const content = await fs.readFile(absolutePath, 'utf-8');\n files.push({\n localPath,\n absolutePath,\n content,\n sha256: sha256(content),\n size: Buffer.byteLength(content, 'utf-8'),\n });\n }\n }\n}\n\nasync function writeMiniState(\n root: string,\n context: MiniProjectContext,\n files: MiniLocalFile[],\n remoteByLocalPath: Map<string, WorkspaceFileItem>,\n): Promise<void> {\n const stateDir = path.join(root, MINI_STATE_DIR);\n const baseDir = path.join(stateDir, 'base');\n await fs.rm(baseDir, { recursive: true, force: true });\n await fs.mkdir(baseDir, { recursive: true });\n const snapshots: MiniFileSnapshot[] = [];\n for (const file of files) {\n await writeText(path.join(baseDir, file.localPath), file.content);\n snapshots.push(toSnapshot(context, file, remoteByLocalPath.get(file.localPath)));\n }\n await writeJson(path.join(stateDir, MINI_PULL_MANIFEST_FILE), {\n version: MINI_CONTEXT_VERSION,\n appCode: context.appCode,\n workspaceKey: context.workspaceKey,\n remotePrefix: context.remotePrefix,\n profile: context.profile,\n pulledAt: new Date().toISOString(),\n files: snapshots,\n } satisfies MiniPullManifest);\n}\n\nfunction toSnapshot(\n context: MiniProjectContext,\n file: MiniLocalFile,\n remote?: WorkspaceFileItem,\n): MiniFileSnapshot {\n return {\n localPath: file.localPath,\n remotePath: remote?.relativePath ?? remotePathFromLocal(context.appCode, file.localPath),\n fileId: remote?.id,\n sha256: file.sha256,\n size: file.size,\n lastModified: remote?.lastModified,\n };\n}\n\nfunction buildMiniAppTemplateFiles(args: {\n appCode: string;\n name?: string;\n defaultSurface: MiniSurface;\n templateKind?: MiniTemplateKind;\n writableModel?: string;\n writableFields?: string[] | null;\n writableDefaults?: Record<string, unknown>;\n writableTodayDefaultFields?: string[];\n requiredScopes: string[];\n tenantId: string;\n}): Array<{ localPath: string; content: string }> {\n return buildMiniAppTemplateFileSet(args);\n}\n\nasync function ensureTargetWritable(\n targetDir: string,\n relativePaths: string[],\n force: boolean,\n): Promise<void> {\n if (force) return;\n const existing: string[] = [];\n for (const relativePath of relativePaths) {\n try {\n await fs.access(path.join(targetDir, relativePath));\n existing.push(relativePath);\n } catch {\n /* file does not exist */\n }\n }\n if (existing.length) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: `target contains existing files: ${existing.join(', ')} (use --force to overwrite)`,\n });\n }\n}\n\nfunction validateRequiredPath(value: string, fieldName: string): string {\n if (!value || !value.trim()) {\n throw new CliError({ code: CliErrorCode.MISSING_FLAG, message: `${fieldName} is required` });\n }\n return value.trim();\n}\n\nfunction normalizeAppCode(value: string): string {\n const normalized = validateRequiredPath(value, 'appCode');\n if (!MINI_APP_CODE_PATTERN.test(normalized)) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: 'appCode must match ^[a-z][a-z0-9-]*$',\n });\n }\n return normalized;\n}\n\nfunction normalizeWorkspaceKey(value: string): string {\n const normalized = validateRequiredPath(value, 'workspaceKey');\n if (!/^[A-Za-z0-9._-]+$/.test(normalized)) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: 'workspaceKey only supports letters, numbers, dot, underscore and dash',\n });\n }\n return normalized;\n}\n\nfunction normalizeRelativePath(value: string): string {\n const normalized = toPosix(value).replace(/^\\/+/, '').replace(/\\/+/g, '/');\n if (!normalized || normalized === '.' || normalized.includes('\\0')) {\n throw new Error('path is empty');\n }\n for (const segment of normalized.split('/')) {\n if (!segment || segment === '.' || segment === '..') {\n throw new Error(`unsafe path segment: ${segment}`);\n }\n }\n return normalized;\n}\n\nfunction miniRemotePrefix(appCode: string): string {\n return `${MINI_REMOTE_ROOT}/${normalizeAppCode(appCode)}`;\n}\n\nfunction remotePathFromLocal(appCode: string, localPath: string): string {\n return `${miniRemotePrefix(appCode)}/${normalizeRelativePath(localPath)}`;\n}\n\nfunction localPathFromRemote(remotePrefix: string, remotePath: string): string {\n const prefix = remotePrefix + '/';\n if (!remotePath.startsWith(prefix)) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: `remote path is outside mini app prefix: ${remotePath}`,\n });\n }\n return normalizeRelativePath(remotePath.slice(prefix.length));\n}\n\nfunction isDirectoryMarkerPath(relativePath: string): boolean {\n return relativePath.endsWith('/.keep') || relativePath === '.keep';\n}\n\nfunction sha256(content: string): string {\n return crypto.createHash('sha256').update(content, 'utf-8').digest('hex');\n}\n\nasync function writeText(file: string, content: string): Promise<void> {\n await fs.mkdir(path.dirname(file), { recursive: true });\n await fs.writeFile(file, content, 'utf-8');\n}\n\nasync function writeJson(file: string, value: unknown): Promise<void> {\n await writeText(file, JSON.stringify(value, null, 2) + '\\n');\n}\n\nfunction conflict(entry: MiniStatusEntry, reason: string): MiniPushConflict {\n return {\n localPath: entry.localPath,\n remotePath: entry.remotePath,\n reason,\n };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};\n}\n\nfunction asStringArray(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];\n}\n\nfunction requireString(\n obj: Record<string, unknown>,\n key: string,\n errors: string[],\n predicate?: (value: string) => boolean,\n): void {\n const value = obj[key];\n if (typeof value !== 'string' || !value.trim()) {\n errors.push(`manifest ${key} is required`);\n return;\n }\n if (predicate && !predicate(value)) {\n errors.push(`manifest ${key} is invalid`);\n }\n}\n\nfunction stringRequired(value: unknown, fieldName: string): string {\n if (typeof value !== 'string' || !value.trim()) {\n throw new CliError({\n code: CliErrorCode.CONFIG_PARSE_ERROR,\n message: `${MINI_CONTEXT_FILE} missing ${fieldName}`,\n });\n }\n return value;\n}\n\nfunction stringOptional(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined;\n}\n\nfunction toPosix(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n","import type { FieldMeta, ModelDescription } from '../meta/describe';\nimport type { MiniSurface, MiniTemplateKind } from './types';\nimport { SDK_VERSION } from '../package-version';\n\nexport interface MiniTemplateArgs {\n appCode: string;\n name?: string;\n defaultSurface: MiniSurface;\n requiredScopes: string[];\n tenantId: string;\n templateKind?: MiniTemplateKind;\n writableModel?: string;\n writableFields?: string[] | null;\n writableDefaults?: Record<string, unknown>;\n writableTodayDefaultFields?: string[];\n}\n\nexport interface MiniWritableTemplateConfig {\n model?: string;\n fields?: string[] | null;\n defaults?: Record<string, unknown>;\n todayDefaultFields?: string[];\n}\n\ninterface MiniReadTemplateConfig {\n model: string;\n modelLabel: string;\n}\n\nconst DEFAULT_MULTI_PAGE_MODEL = 'Employee';\nconst DEFAULT_WRITABLE_MODEL = 'OrgUnit';\n// OrgUnit 的编号字段是 `code`(CLAUDE.md 字段命名规范:编号/编码一律 code)。\n// 后端 OrgUnit.yml 里 unitCode 仅作 AI synonym 存在,不是可写字段——\n// 曾误用 unitCode 导致 writable-workbench 模板生成绑定到不存在字段的表单。\nconst DEFAULT_WRITABLE_FIELDS = ['code', 'name', 'unitType', 'status', 'effecDate', 'expireDate'];\nconst DEFAULT_WRITABLE_DEFAULTS: Record<string, unknown> = {\n unitType: 'DEPT',\n status: 'ACTIVE',\n expireDate: '9999-12-31',\n};\nconst DEFAULT_WRITABLE_TODAY_FIELDS = ['effecDate'];\nconst SYSTEM_FIELD_NAMES = new Set([\n 'id',\n 'tenantId',\n 'createdAt',\n 'createdBy',\n 'updatedAt',\n 'updatedBy',\n 'deletedAt',\n 'deletedBy',\n 'version',\n]);\nconst PREFERRED_WRITABLE_FIELDS = [\n 'code',\n 'name',\n 'unitType',\n 'status',\n 'effecDate',\n 'effectiveDate',\n 'startDate',\n 'expireDate',\n 'endDate',\n 'description',\n 'remark',\n 'notes',\n];\nconst MAX_AUTO_WRITABLE_FIELDS = 10;\nconst REFERENCE_FIELD_MODELS: Record<string, string> = {\n employeeId: 'Employee',\n managerId: 'Employee',\n orgUnitId: 'OrgUnit',\n organizationId: 'OrgUnit',\n positionId: 'Position',\n reportToId: 'Position',\n jobId: 'Job',\n jobGradeId: 'JobGrade',\n jobFamilyId: 'JobFamily',\n};\n\nexport function defaultMiniRequiredScopes(\n templateKind: MiniTemplateKind = 'dashboard',\n writableConfig: MiniWritableTemplateConfig = {},\n): string[] {\n if (templateKind === 'writable-workbench') {\n const model = normalizeWritableModel(writableConfig.model);\n return unique([\n `${model}.list`,\n `${model}.get`,\n `${model}.create`,\n `${model}.update`,\n `${model}.delete`,\n ...referenceScopesForWritableFields(writableConfig.fields),\n ]);\n }\n if (templateKind === 'multi-page') {\n const model = normalizeReadModel(writableConfig.model);\n return [`${model}.list`, `${model}.get`];\n }\n return ['Employee.list'];\n}\n\nexport function buildMiniAppTemplateFiles(args: MiniTemplateArgs): Array<{ localPath: string; content: string }> {\n const templateKind = args.templateKind ?? 'dashboard';\n const displayName = args.name?.trim() || args.appCode;\n const writableConfig = buildWritableTemplateConfig(args);\n const readConfig = buildReadTemplateConfig(args);\n const manifest = {\n schemaVersion: '1.0',\n code: args.appCode,\n name: displayName,\n version: SDK_VERSION,\n sdkVersion: SDK_VERSION,\n surfaces: ['embedded', 'standalone'],\n defaultSurface: args.defaultSurface,\n entry: { html: 'index.html', css: 'index.css', js: 'index.js' },\n bundleObjectRef: {\n tenantId: args.tenantId,\n storageType: 'LOCAL',\n pathSpec: { pathSegment: `mini_apps/${args.appCode}`, fileName: '' },\n },\n requiredScopes: args.requiredScopes,\n };\n\n return [\n { localPath: 'manifest.json', content: JSON.stringify(manifest, null, 2) + '\\n' },\n {\n localPath: 'index.html',\n content: templateHtml(templateKind, args.appCode, displayName, writableConfig),\n },\n {\n localPath: 'index.css',\n content: templateCss(templateKind),\n },\n {\n localPath: 'index.js',\n content: templateJs(templateKind, args.appCode, displayName, writableConfig, readConfig),\n },\n ];\n}\n\nfunction templateHtml(\n templateKind: MiniTemplateKind,\n appCode: string,\n displayName: string,\n writableConfig: ReturnType<typeof buildWritableTemplateConfig>,\n): string {\n if (templateKind === 'writable-workbench') return writableHtml(appCode, displayName, writableConfig);\n if (templateKind === 'multi-page') return multiPageHtml(appCode, displayName);\n return dashboardHtml(appCode, displayName);\n}\n\nfunction templateCss(templateKind: MiniTemplateKind): string {\n if (templateKind === 'writable-workbench') return writableCss();\n if (templateKind === 'multi-page') return multiPageCss();\n return dashboardCss();\n}\n\nfunction templateJs(\n templateKind: MiniTemplateKind,\n appCode: string,\n displayName: string,\n writableConfig: ReturnType<typeof buildWritableTemplateConfig>,\n readConfig: MiniReadTemplateConfig,\n): string {\n if (templateKind === 'writable-workbench') return writableJs(writableConfig);\n if (templateKind === 'multi-page') return multiPageJs(appCode, displayName, readConfig);\n return dashboardJs();\n}\n\nfunction buildReadTemplateConfig(args: MiniTemplateArgs): MiniReadTemplateConfig {\n const model = normalizeReadModel(args.writableModel);\n return {\n model,\n modelLabel: modelDisplayLabel(model),\n };\n}\n\nfunction buildWritableTemplateConfig(args: MiniTemplateArgs): Required<MiniWritableTemplateConfig> & { modelLabel: string } {\n const model = normalizeWritableModel(args.writableModel);\n const fields =\n args.writableFields === undefined\n ? model === DEFAULT_WRITABLE_MODEL\n ? [...DEFAULT_WRITABLE_FIELDS]\n : null\n : args.writableFields;\n const defaults =\n args.writableDefaults !== undefined\n ? args.writableDefaults\n : model === DEFAULT_WRITABLE_MODEL\n ? { ...DEFAULT_WRITABLE_DEFAULTS }\n : {};\n const todayDefaultFields =\n args.writableTodayDefaultFields !== undefined\n ? args.writableTodayDefaultFields\n : model === DEFAULT_WRITABLE_MODEL\n ? [...DEFAULT_WRITABLE_TODAY_FIELDS]\n : [];\n return {\n model,\n fields,\n defaults,\n todayDefaultFields,\n modelLabel: modelDisplayLabel(model),\n };\n}\n\nexport function inferMiniWritableTemplateOptionsFromModel(\n description: ModelDescription,\n): { fields: string[]; defaults: Record<string, unknown>; todayDefaultFields: string[] } {\n const writableFields = inferWritableFields(description.fields);\n const writableFieldSet = new Set(writableFields);\n return {\n fields: writableFields,\n defaults: inferWritableDefaults(description.fields, writableFieldSet),\n todayDefaultFields: inferTodayDefaultFields(description.fields, writableFieldSet),\n };\n}\n\nfunction inferWritableFields(fields: FieldMeta[]): string[] {\n const editable = fields.map((field) => field.name).filter((name) => name && !SYSTEM_FIELD_NAMES.has(name));\n const editableSet = new Set(editable);\n const required = fields\n .filter((field) => field.required === true && editableSet.has(field.name))\n .map((field) => field.name);\n const preferred = PREFERRED_WRITABLE_FIELDS.filter((name) => editableSet.has(name));\n const inferred = unique([...required, ...preferred]);\n return (inferred.length ? inferred : editable).slice(0, MAX_AUTO_WRITABLE_FIELDS);\n}\n\nfunction inferWritableDefaults(fields: FieldMeta[], writableFieldSet: Set<string>): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n if (hasWritableField(fields, writableFieldSet, 'unitType')) defaults.unitType = 'DEPT';\n if (hasWritableField(fields, writableFieldSet, 'status')) defaults.status = 'ACTIVE';\n if (hasWritableField(fields, writableFieldSet, 'expireDate')) defaults.expireDate = '9999-12-31';\n if (hasWritableField(fields, writableFieldSet, 'endDate')) defaults.endDate = '9999-12-31';\n return defaults;\n}\n\nfunction inferTodayDefaultFields(fields: FieldMeta[], writableFieldSet: Set<string>): string[] {\n return ['effecDate', 'effectiveDate', 'startDate'].filter((name) => hasWritableField(fields, writableFieldSet, name));\n}\n\nfunction hasWritableField(fields: FieldMeta[], writableFieldSet: Set<string>, name: string): boolean {\n return writableFieldSet.has(name) && fields.some((field) => field.name === name);\n}\n\nfunction unique(values: string[]): string[] {\n return Array.from(new Set(values));\n}\n\nfunction referenceScopesForWritableFields(fields?: string[] | null): string[] {\n if (!fields?.length) return [];\n return unique(\n fields.flatMap((field) => {\n const model = REFERENCE_FIELD_MODELS[field];\n return model ? [`${model}.list`, `${model}.get`] : [];\n }),\n );\n}\n\nfunction normalizeWritableModel(model?: string): string {\n return model?.trim() || DEFAULT_WRITABLE_MODEL;\n}\n\nfunction normalizeReadModel(model?: string): string {\n return model?.trim() || DEFAULT_MULTI_PAGE_MODEL;\n}\n\nfunction modelDisplayLabel(model: string): string {\n const labels: Record<string, string> = {\n Employee: '员工',\n OrgUnit: '组织单元',\n Position: '岗位',\n };\n return labels[model] ?? `${model} 记录`;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n}\n\nfunction dashboardHtml(appCode: string, displayName: string): string {\n return `<section class=\"hcm-mini-app\" data-mini-app=\"${appCode}\">\n <header class=\"hcm-mini-app__header\">\n <p class=\"hcm-mini-app__eyebrow\">HCM Mini App</p>\n <h1>${displayName}</h1>\n </header>\n <main class=\"hcm-mini-app__body\">\n <span data-field=\"status\">Ready</span>\n </main>\n</section>\n`;\n}\n\nfunction dashboardCss(): string {\n return `.hcm-mini-app {\n min-height: 100%;\n padding: 24px;\n color: #1f2933;\n background: #f6f8fb;\n font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n}\n\n.hcm-mini-app__header {\n margin-bottom: 16px;\n}\n\n.hcm-mini-app__eyebrow {\n margin: 0 0 4px;\n color: #52606d;\n font-size: 12px;\n}\n\n.hcm-mini-app h1 {\n margin: 0;\n font-size: 24px;\n line-height: 1.25;\n}\n`;\n}\n\nfunction dashboardJs(): string {\n return `export async function mount(container, hcm) {\n const status = container.querySelector('[data-field=\"status\"]');\n if (status) {\n status.textContent = \"Tenant \" + hcm.context.tenantId;\n }\n}\n\nexport function unmount(container) {\n container.innerHTML = \"\";\n}\n`;\n}\n\nfunction multiPageHtml(appCode: string, displayName: string): string {\n return `<section class=\"mini-routed-app\" data-mini-app=\"${appCode}\">\n <header class=\"mini-routed-app__header\">\n <div>\n <p class=\"mini-routed-app__eyebrow\">Tenant <span data-field=\"tenant-id\">-</span></p>\n <h1>${escapeHtml(displayName)}</h1>\n </div>\n <span class=\"mini-routed-app__status\" data-field=\"status\">准备就绪</span>\n </header>\n\n <nav class=\"mini-routed-app__nav\" data-field=\"nav\" aria-label=\"轻应用页面\"></nav>\n <main class=\"mini-routed-app__view\" data-field=\"view\"></main>\n <pre class=\"mini-routed-app__error\" data-field=\"error\" hidden></pre>\n</section>\n`;\n}\n\nfunction multiPageCss(): string {\n return `.mini-routed-app {\n min-height: 100%;\n padding: 24px;\n color: #1f2933;\n background: #f6f8fb;\n font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n}\n\n.mini-routed-app__header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 16px;\n}\n\n.mini-routed-app__eyebrow,\n.mini-routed-app__status {\n margin: 0;\n color: #52606d;\n font-size: 12px;\n}\n\n.mini-routed-app h1 {\n margin: 4px 0 0;\n font-size: 24px;\n line-height: 1.2;\n}\n\n.mini-routed-app__nav {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-bottom: 14px;\n}\n\n.mini-routed-app__nav a {\n border: 1px solid #d9e2ec;\n border-radius: 6px;\n padding: 7px 12px;\n color: #52606d;\n background: #fff;\n text-decoration: none;\n}\n\n.mini-routed-app__nav a[aria-current=\"page\"] {\n border-color: #2f80ed;\n color: #fff;\n background: #2f80ed;\n}\n\n.mini-routed-app__view {\n min-height: 320px;\n border: 1px solid #d9e2ec;\n border-radius: 8px;\n padding: 18px;\n background: #fff;\n}\n\n.mini-routed-app__toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 14px;\n}\n\n.mini-routed-app__grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 12px;\n}\n\n.mini-routed-app__metric,\n.mini-routed-app__section {\n border: 1px solid #e5edf5;\n border-radius: 8px;\n padding: 14px;\n background: #fbfdff;\n}\n\n.mini-routed-app__metric span,\n.mini-routed-app__section span {\n display: block;\n color: #8a94a6;\n font-size: 12px;\n}\n\n.mini-routed-app__metric strong {\n display: block;\n margin-top: 6px;\n font-size: 24px;\n}\n\n.mini-routed-app__list {\n display: grid;\n gap: 8px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.mini-routed-app__list button,\n.mini-routed-app__button {\n height: 34px;\n border: 1px solid #2f80ed;\n border-radius: 6px;\n padding: 0 12px;\n color: #fff;\n background: #2f80ed;\n cursor: pointer;\n}\n\n.mini-routed-app__list button {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n border-color: #d9e2ec;\n color: #1f2933;\n background: #f8fafc;\n text-align: left;\n}\n\n.mini-routed-app__muted {\n color: #8a94a6;\n}\n\n.mini-routed-app__error {\n margin: 14px 0 0;\n border: 1px solid #fecaca;\n border-radius: 6px;\n padding: 10px;\n color: #b91c1c;\n background: #fff1f2;\n white-space: pre-wrap;\n}\n\n@media (max-width: 720px) {\n .mini-routed-app {\n padding: 16px;\n }\n\n .mini-routed-app__header,\n .mini-routed-app__toolbar {\n display: grid;\n }\n}\n`;\n}\n\nfunction multiPageJs(appCode: string, displayName: string, config: MiniReadTemplateConfig): string {\n return `const APP_CODE = ${JSON.stringify(appCode)};\nconst APP_NAME = ${JSON.stringify(displayName)};\nconst DATA_MODEL = ${JSON.stringify(config.model)};\nconst MODEL_LABEL = ${JSON.stringify(config.modelLabel)};\nconst ROUTES = [\n { path: \"/\", label: \"总览\" },\n { path: \"/records\", label: MODEL_LABEL },\n { path: \"/settings\", label: \"设置\" },\n];\n\nconst state = {\n records: [],\n loaded: false,\n details: new Map(),\n unsubscribe: null,\n};\n\nconst textOf = (value, fallback = \"-\") => String(value ?? fallback);\nconst setText = (container, selector, value) => {\n const node = container.querySelector(selector);\n if (node) node.textContent = textOf(value);\n};\nconst setStatus = (container, value) => setText(container, '[data-field=\"status\"]', value);\nconst showError = (container, error) => {\n const node = container.querySelector('[data-field=\"error\"]');\n if (!node) return;\n if (!error) {\n node.hidden = true;\n node.textContent = \"\";\n return;\n }\n node.hidden = false;\n node.textContent = [error.code || \"REQUEST_FAILED\", error.message || \"请求失败\", error.traceId ? \"traceId: \" + error.traceId : \"\"].filter(Boolean).join(\"\\\\n\");\n};\nconst viewOf = (container) => container.querySelector('[data-field=\"view\"]');\nconst createNode = (tag, className, text) => {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (text !== undefined) node.textContent = text;\n return node;\n};\nconst recordName = (record) => record.name || record.employeeName || record.code || record.unitCode || record.positionCode || record.employeeNo || record.id || \"未命名\" + MODEL_LABEL;\n\nconst renderNav = (container, hcm) => {\n const nav = container.querySelector('[data-field=\"nav\"]');\n if (!nav) return;\n const current = hcm.router.current().path;\n nav.innerHTML = \"\";\n for (const route of ROUTES) {\n const link = document.createElement(\"a\");\n link.href = hcm.router.link(route.path);\n link.dataset.route = route.path;\n link.textContent = route.label;\n const active = route.path === \"/\" ? current === \"/\" : current === route.path || current.startsWith(route.path + \"/\");\n if (active) link.setAttribute(\"aria-current\", \"page\");\n nav.appendChild(link);\n }\n};\n\nconst loadRecords = async (container, hcm) => {\n if (state.loaded) return;\n setStatus(container, \"加载中\");\n const result = await hcm.query({ model: DATA_MODEL, page: 0, size: 10 });\n state.records = result.rows || [];\n state.loaded = true;\n setStatus(container, \"已更新\");\n};\n\nconst loadDetail = async (hcm, id) => {\n if (!state.details.has(id)) {\n state.details.set(id, await hcm.get({ model: DATA_MODEL, id }));\n }\n return state.details.get(id);\n};\n\nconst renderOverview = (container, hcm) => {\n const view = viewOf(container);\n if (!view) return;\n view.innerHTML = \"\";\n const title = createNode(\"div\", \"mini-routed-app__toolbar\");\n const heading = createNode(\"h2\", \"\", \"总览\");\n const action = createNode(\"button\", \"mini-routed-app__button\", \"查看\" + MODEL_LABEL);\n action.type = \"button\";\n action.dataset.route = \"/records\";\n title.appendChild(heading);\n title.appendChild(action);\n\n const grid = createNode(\"div\", \"mini-routed-app__grid\");\n const metrics = [\n [\"应用\", APP_NAME],\n [\"数据模型\", DATA_MODEL],\n [\"当前租户\", hcm.context.tenantId],\n ];\n for (const [label, value] of metrics) {\n const card = createNode(\"div\", \"mini-routed-app__metric\");\n card.appendChild(createNode(\"span\", \"\", label));\n card.appendChild(createNode(\"strong\", \"\", value));\n grid.appendChild(card);\n }\n view.appendChild(title);\n view.appendChild(grid);\n};\n\nconst renderRecords = async (container, hcm) => {\n await loadRecords(container, hcm);\n const view = viewOf(container);\n if (!view) return;\n view.innerHTML = \"\";\n const toolbar = createNode(\"div\", \"mini-routed-app__toolbar\");\n toolbar.appendChild(createNode(\"h2\", \"\", MODEL_LABEL));\n toolbar.appendChild(createNode(\"span\", \"mini-routed-app__muted\", \"共 \" + state.records.length + \" 条\"));\n const list = createNode(\"ul\", \"mini-routed-app__list\");\n for (const record of state.records) {\n const item = document.createElement(\"li\");\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.dataset.recordId = textOf(record.id, \"\");\n button.appendChild(createNode(\"strong\", \"\", recordName(record)));\n button.appendChild(createNode(\"span\", \"mini-routed-app__muted\", record.status || record.employeeNo || record.code || \"详情\"));\n item.appendChild(button);\n list.appendChild(item);\n }\n if (!state.records.length) {\n list.appendChild(createNode(\"li\", \"mini-routed-app__muted\", \"暂无\" + MODEL_LABEL + \"记录\"));\n }\n view.appendChild(toolbar);\n view.appendChild(list);\n};\n\nconst renderRecordDetail = async (container, hcm, id) => {\n const record = await loadDetail(hcm, id);\n const view = viewOf(container);\n if (!view) return;\n view.innerHTML = \"\";\n const toolbar = createNode(\"div\", \"mini-routed-app__toolbar\");\n const title = createNode(\"h2\", \"\", recordName(record));\n const back = createNode(\"button\", \"mini-routed-app__button\", \"返回列表\");\n back.type = \"button\";\n back.dataset.route = \"/records\";\n toolbar.appendChild(title);\n toolbar.appendChild(back);\n\n const grid = createNode(\"div\", \"mini-routed-app__grid\");\n const fields = [\n [\"ID\", record.id],\n [\"编号\", record.employeeNo || record.code],\n [\"状态\", record.status],\n [\"邮箱\", record.email],\n ];\n for (const [label, value] of fields) {\n const card = createNode(\"div\", \"mini-routed-app__section\");\n card.appendChild(createNode(\"span\", \"\", label));\n card.appendChild(createNode(\"strong\", \"\", textOf(value)));\n grid.appendChild(card);\n }\n view.appendChild(toolbar);\n view.appendChild(grid);\n};\n\nconst renderSettings = (container, hcm) => {\n const view = viewOf(container);\n if (!view) return;\n view.innerHTML = \"\";\n const section = createNode(\"div\", \"mini-routed-app__section\");\n section.appendChild(createNode(\"h2\", \"\", \"设置\"));\n section.appendChild(createNode(\"span\", \"\", \"应用编码\"));\n section.appendChild(createNode(\"strong\", \"\", APP_CODE));\n section.appendChild(createNode(\"span\", \"\", \"当前路由\"));\n section.appendChild(createNode(\"strong\", \"\", hcm.router.current().path));\n view.appendChild(section);\n};\n\nconst renderRoute = async (container, hcm) => {\n showError(container, null);\n renderNav(container, hcm);\n const detailRoute = hcm.router.match(\"/records/:id\");\n if (detailRoute) {\n await renderRecordDetail(container, hcm, detailRoute.params.id);\n return;\n }\n if (hcm.router.match(\"/records\")) {\n await renderRecords(container, hcm);\n return;\n }\n if (hcm.router.match(\"/settings\")) {\n renderSettings(container, hcm);\n return;\n }\n if (hcm.router.current().path !== \"/\") {\n hcm.router.replace(\"/\");\n return;\n }\n renderOverview(container, hcm);\n};\n\nexport async function mount(container, hcm) {\n setText(container, '[data-field=\"tenant-id\"]', hcm.context.tenantId);\n const onClick = (event) => {\n const routeTarget = event.target instanceof Element ? event.target.closest(\"[data-route]\") : null;\n if (routeTarget?.dataset.route) {\n event.preventDefault();\n hcm.router.push(routeTarget.dataset.route);\n return;\n }\n const recordTarget = event.target instanceof Element ? event.target.closest(\"[data-record-id]\") : null;\n if (recordTarget?.dataset.recordId) {\n hcm.router.push(\"/records/\" + encodeURIComponent(recordTarget.dataset.recordId));\n }\n };\n container.addEventListener(\"click\", onClick);\n state.unsubscribe = hcm.router.subscribe(() => void renderRoute(container, hcm).catch((error) => showError(container, error)));\n await renderRoute(container, hcm);\n container.__miniCleanup = () => {\n container.removeEventListener(\"click\", onClick);\n state.unsubscribe?.();\n };\n}\n\nexport function unmount(container) {\n container.__miniCleanup?.();\n container.innerHTML = \"\";\n}\n`;\n}\n\nfunction writableHtml(appCode: string, displayName: string, config: ReturnType<typeof buildWritableTemplateConfig>): string {\n const modelLabel = escapeHtml(config.modelLabel);\n return `<section class=\"mini-workbench\" data-mini-app=\"${appCode}\">\n <header class=\"mini-workbench__header\">\n <div>\n <p class=\"mini-workbench__eyebrow\">Tenant <span data-field=\"tenant-id\">-</span></p>\n <h1>${escapeHtml(displayName)}</h1>\n </div>\n <span class=\"mini-workbench__status\" data-field=\"status\">准备就绪</span>\n </header>\n\n <div class=\"mini-workbench__layout\">\n <aside class=\"mini-workbench__panel\">\n <div class=\"mini-workbench__panel-head\">\n <strong>${modelLabel}</strong>\n <div class=\"mini-workbench__actions\">\n <button type=\"button\" data-action=\"new-record\">新建</button>\n <button type=\"button\" data-action=\"refresh\">刷新</button>\n </div>\n </div>\n <div class=\"mini-workbench__list\" data-field=\"record-list\"></div>\n </aside>\n\n <main class=\"mini-workbench__panel\">\n <div class=\"mini-workbench__panel-head\">\n <strong data-field=\"editor-title\">选择${modelLabel}</strong>\n <div class=\"mini-workbench__actions\">\n <button type=\"button\" data-action=\"delete-record\" hidden>删除</button>\n <button type=\"button\" data-action=\"back\">返回列表</button>\n </div>\n </div>\n <div class=\"mini-workbench__empty\" data-field=\"editor-empty\">\n <strong>选择一条${modelLabel}</strong>\n <span>从左侧列表打开记录,或点击“新建”。</span>\n </div>\n <form class=\"mini-workbench__form\" data-field=\"editor-form\" hidden>\n <div class=\"mini-workbench__fields\" data-field=\"editor-fields\"></div>\n <button type=\"submit\">保存</button>\n </form>\n <pre class=\"mini-workbench__error\" data-field=\"error\" hidden></pre>\n </main>\n </div>\n</section>\n`;\n}\n\nfunction writableCss(): string {\n return `.mini-workbench {\n min-height: 100%;\n padding: 24px;\n color: #1f2933;\n background: #f6f8fb;\n font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n}\n\n.mini-workbench__header,\n.mini-workbench__panel-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n}\n\n.mini-workbench__header {\n margin-bottom: 18px;\n}\n\n.mini-workbench__eyebrow,\n.mini-workbench__status {\n margin: 0;\n color: #52606d;\n font-size: 12px;\n}\n\n.mini-workbench h1 {\n margin: 4px 0 0;\n font-size: 24px;\n line-height: 1.2;\n}\n\n.mini-workbench__layout {\n display: grid;\n grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);\n gap: 14px;\n}\n\n.mini-workbench__panel {\n min-height: 320px;\n border: 1px solid #d9e2ec;\n border-radius: 8px;\n padding: 16px;\n background: #fff;\n}\n\n.mini-workbench__actions,\n.mini-workbench__form,\n.mini-workbench__fields {\n display: grid;\n gap: 10px;\n}\n\n.mini-workbench__form[hidden],\n.mini-workbench__empty[hidden] {\n display: none;\n}\n\n.mini-workbench__actions {\n grid-auto-flow: column;\n align-items: center;\n}\n\n.mini-workbench button {\n height: 32px;\n border: 1px solid #2f80ed;\n border-radius: 6px;\n padding: 0 12px;\n color: #fff;\n background: #2f80ed;\n cursor: pointer;\n}\n\n.mini-workbench__list {\n display: grid;\n gap: 8px;\n}\n\n.mini-workbench__empty {\n display: grid;\n gap: 8px;\n min-height: 220px;\n place-content: center;\n color: #8a94a6;\n text-align: center;\n}\n\n.mini-workbench__empty strong {\n color: #52606d;\n font-size: 15px;\n}\n\n.mini-workbench__list button {\n width: 100%;\n border-color: #d9e2ec;\n color: #1f2933;\n background: #f8fafc;\n text-align: left;\n}\n\n.mini-workbench__field {\n display: grid;\n gap: 6px;\n color: #52606d;\n font-size: 13px;\n}\n\n.mini-workbench__field input,\n.mini-workbench__field select,\n.mini-workbench__field textarea {\n min-height: 36px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n padding: 0 10px;\n color: #1f2933;\n background: #fff;\n}\n\n.mini-workbench__field textarea {\n min-height: 72px;\n padding: 8px 10px;\n}\n\n.mini-workbench__field--checkbox {\n grid-template-columns: auto 1fr;\n align-items: center;\n}\n\n.mini-workbench__field--checkbox input {\n min-height: auto;\n}\n\n.mini-workbench__field-hint {\n color: #8a94a6;\n font-size: 12px;\n}\n\n.mini-workbench__reference {\n display: grid;\n gap: 6px;\n position: relative;\n}\n\n.mini-workbench__reference-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 8px;\n}\n\n.mini-workbench__reference-results {\n display: grid;\n gap: 4px;\n max-height: 180px;\n overflow: auto;\n border: 1px solid #d9e2ec;\n border-radius: 6px;\n padding: 6px;\n background: #fff;\n}\n\n.mini-workbench__reference-results[hidden] {\n display: none;\n}\n\n.mini-workbench__reference-results button {\n width: 100%;\n border-color: #d9e2ec;\n color: #1f2933;\n background: #f8fafc;\n text-align: left;\n}\n\n.mini-workbench__required {\n color: #dc2626;\n}\n\n.mini-workbench__error {\n margin: 14px 0 0;\n border: 1px solid #fecaca;\n border-radius: 6px;\n padding: 10px;\n color: #b91c1c;\n background: #fff1f2;\n white-space: pre-wrap;\n}\n\n@media (max-width: 720px) {\n .mini-workbench__layout {\n grid-template-columns: 1fr;\n }\n}\n`;\n}\n\nfunction writableJs(config: ReturnType<typeof buildWritableTemplateConfig>): string {\n const model = JSON.stringify(config.model);\n const modelLabel = JSON.stringify(config.modelLabel);\n const fields = config.fields?.length ? JSON.stringify(config.fields) : 'null';\n const defaults = JSON.stringify(config.defaults, null, 2);\n const todayDefaults = JSON.stringify(config.todayDefaultFields);\n return `const MODEL = ${model};\nconst MODEL_LABEL = ${modelLabel};\nconst ROUTE_BASE = \"/records\";\nconst FORM_FIELDS = ${fields};\nconst CREATE_DEFAULTS = ${defaults};\nconst TODAY_DEFAULT_FIELDS = ${todayDefaults};\nconst state = {\n records: [],\n activeId: \"\",\n mode: \"list\",\n meta: null,\n unsubscribe: null,\n};\nconst textOf = (value, fallback = \"-\") => String(value ?? fallback);\nconst today = () => new Date().toISOString().slice(0, 10);\nconst formDefaults = () => {\n const defaults = { ...CREATE_DEFAULTS };\n for (const key of TODAY_DEFAULT_FIELDS) defaults[key] = today();\n return defaults;\n};\nconst formOptions = (defaults = {}) => {\n const options = {};\n if (Array.isArray(FORM_FIELDS) && FORM_FIELDS.length) options.include = FORM_FIELDS;\n if (defaults && Object.keys(defaults).length) options.defaults = defaults;\n return options;\n};\nconst setText = (container, selector, value) => {\n const node = container.querySelector(selector);\n if (node) node.textContent = textOf(value);\n};\nconst setStatus = (container, value) => setText(container, '[data-field=\"status\"]', value);\nconst setDeleteVisible = (container, visible) => {\n const button = container.querySelector('[data-action=\"delete-record\"]');\n if (button) button.hidden = !visible;\n};\nconst setEditorVisible = (container, visible) => {\n const form = container.querySelector('[data-field=\"editor-form\"]');\n const empty = container.querySelector('[data-field=\"editor-empty\"]');\n if (form) form.hidden = !visible;\n if (empty) empty.hidden = visible;\n};\nconst showError = (container, error) => {\n const node = container.querySelector('[data-field=\"error\"]');\n if (!node) return;\n if (!error) {\n node.hidden = true;\n node.textContent = \"\";\n return;\n }\n node.hidden = false;\n node.textContent = [error.code || \"REQUEST_FAILED\", error.message || \"请求失败\", error.traceId ? \"traceId: \" + error.traceId : \"\"].filter(Boolean).join(\"\\\\n\");\n};\nconst ensureMeta = async (hcm) => {\n if (!state.meta) state.meta = await hcm.describe({ model: MODEL, type: \"info\" });\n return state.meta;\n};\nconst queryReferenceRecords = async (hcm, field, keyword) => {\n const model = field.reference?.model;\n if (!model) return [];\n if (hcm.reference?.search) {\n return (await hcm.reference.search({ model, field: field.reference?.field, keyword, size: 8 })).rows;\n }\n const query = { model, page: 0, size: 8 };\n const trimmed = keyword.trim();\n if (trimmed) {\n query.filter = { [field.reference?.field || \"name\"]: { $contains: trimmed } };\n }\n try {\n return (await hcm.query(query)).rows;\n } catch (error) {\n if (!trimmed) throw error;\n return (await hcm.query({ model, page: 0, size: 8 })).rows;\n }\n};\nconst referenceLabel = (hcm, record) => hcm.reference?.label ? hcm.reference.label(record) : record?.name || record?.code || record?.unitCode || record?.positionCode || record?.employeeNo || record?.id || \"未命名记录\";\nconst recordName = (record) => record?.name || record?.employeeName || record?.code || record?.unitCode || record?.positionCode || record?.employeeNo || record?.id || \"未命名\" + MODEL_LABEL;\nconst hydrateReferenceLabel = async (hcm, field, searchInput, value) => {\n const id = value === undefined || value === null ? \"\" : String(value).trim();\n if (!id || !field.reference?.model || !hcm.reference?.labelFor) return;\n const label = await hcm.reference.labelFor({ model: field.reference.model, id });\n if (searchInput.value === id) searchInput.value = label || id;\n};\nconst applyReferenceRecord = (hcm, hiddenInput, searchInput, record) => {\n hiddenInput.value = record?.id ?? \"\";\n searchInput.value = referenceLabel(hcm, record);\n};\nconst isUnsupportedMiniUi = (error) => error?.code === \"MINI_UI_UNSUPPORTED\" || error?.name === \"MiniUiUnsupportedError\";\nconst pickReferenceRecord = async (hcm, field) => {\n const model = field.reference?.model;\n if (!model || !hcm.ui?.pickModel) return null;\n try {\n const result = await hcm.ui.pickModel({\n model,\n multiple: false,\n title: \"选择\" + field.label,\n });\n return result.confirmed ? result.record : null;\n } catch (error) {\n if (isUnsupportedMiniUi(error)) return null;\n throw error;\n }\n};\nconst renderReferenceOptions = (hcm, box, hiddenInput, searchInput, records) => {\n box.innerHTML = \"\";\n if (!records.length) {\n box.textContent = \"暂无可选记录\";\n box.hidden = false;\n return;\n }\n for (const record of records) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.textContent = referenceLabel(hcm, record);\n button.addEventListener(\"click\", () => {\n applyReferenceRecord(hcm, hiddenInput, searchInput, record);\n box.hidden = true;\n });\n box.appendChild(button);\n }\n box.hidden = false;\n};\nconst renderReferenceControl = (field, value, hcm) => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = \"mini-workbench__reference\";\n const hidden = document.createElement(\"input\");\n hidden.type = \"hidden\";\n hidden.name = field.key;\n hidden.dataset.formKey = field.key;\n hidden.value = value ?? \"\";\n const search = document.createElement(\"input\");\n search.type = \"search\";\n search.name = field.key + \"__search\";\n search.autocomplete = \"off\";\n search.placeholder = field.reference?.model ? \"搜索 \" + field.reference.model : \"搜索记录\";\n search.value = value ?? \"\";\n void hydrateReferenceLabel(hcm, field, search, value);\n const picker = document.createElement(\"button\");\n picker.type = \"button\";\n picker.textContent = \"选择\";\n const results = document.createElement(\"div\");\n results.className = \"mini-workbench__reference-results\";\n results.hidden = true;\n let requestId = 0;\n const searchRecords = async () => {\n const current = ++requestId;\n results.hidden = false;\n results.textContent = \"搜索中...\";\n try {\n const records = await queryReferenceRecords(hcm, field, search.value);\n if (current === requestId) renderReferenceOptions(hcm, results, hidden, search, records);\n } catch (error) {\n if (current === requestId) {\n results.textContent = \"搜索失败,可直接填写记录 ID\";\n }\n }\n };\n search.addEventListener(\"focus\", () => void searchRecords());\n search.addEventListener(\"input\", () => {\n hidden.value = search.value.trim();\n void searchRecords();\n });\n picker.addEventListener(\"click\", () => {\n void (async () => {\n results.hidden = true;\n const record = await pickReferenceRecord(hcm, field);\n if (record) {\n applyReferenceRecord(hcm, hidden, search, record);\n return;\n }\n search.focus();\n await searchRecords();\n })().catch((error) => {\n results.hidden = false;\n results.textContent = error?.message || \"选择失败,可直接搜索或填写记录 ID\";\n });\n });\n const row = document.createElement(\"div\");\n row.className = \"mini-workbench__reference-row\";\n row.appendChild(search);\n row.appendChild(picker);\n wrapper.appendChild(hidden);\n wrapper.appendChild(row);\n wrapper.appendChild(results);\n return wrapper;\n};\nconst renderFormControl = (field, value, hcm) => {\n const key = field.key;\n if (field.inputType === \"select\" && field.options?.length) {\n const select = document.createElement(\"select\");\n select.name = key;\n select.dataset.formKey = key;\n for (const option of field.options) {\n const item = document.createElement(\"option\");\n item.value = String(option.value);\n item.textContent = option.label;\n item.disabled = option.disabled === true;\n if (String(option.value) === String(value ?? \"\")) item.selected = true;\n select.appendChild(item);\n }\n return select;\n }\n if (field.inputType === \"textarea\") {\n const textarea = document.createElement(\"textarea\");\n textarea.name = key;\n textarea.dataset.formKey = key;\n textarea.value = value ?? \"\";\n return textarea;\n }\n if (field.inputType === \"reference\") {\n return renderReferenceControl(field, value, hcm);\n }\n const input = document.createElement(\"input\");\n input.name = key;\n input.dataset.formKey = key;\n input.autocomplete = \"off\";\n input.type =\n field.inputType === \"number\" ? \"number\" :\n field.inputType === \"date\" ? \"date\" :\n field.inputType === \"datetime\" ? \"datetime-local\" :\n field.inputType === \"checkbox\" ? \"checkbox\" : \"text\";\n if (field.inputType === \"checkbox\") {\n input.checked = value === true || value === \"true\";\n } else {\n input.value = value ?? \"\";\n }\n return input;\n};\nconst renderForm = async (container, hcm, values = {}) => {\n const meta = await ensureMeta(hcm);\n const defaults = formDefaults();\n const fields = hcm.form.fields(meta, formOptions());\n const fieldBox = container.querySelector('[data-field=\"editor-fields\"]');\n if (!fieldBox) return;\n fieldBox.innerHTML = \"\";\n for (const field of fields) {\n const value = values[field.key] ?? defaults[field.key] ?? field.defaultValue ?? \"\";\n const label = document.createElement(\"label\");\n label.className = \"mini-workbench__field\" + (field.inputType === \"checkbox\" ? \" mini-workbench__field--checkbox\" : \"\");\n const caption = document.createElement(\"span\");\n caption.textContent = field.label;\n if (field.required) {\n const mark = document.createElement(\"strong\");\n mark.className = \"mini-workbench__required\";\n mark.textContent = \" *\";\n caption.appendChild(mark);\n }\n label.appendChild(caption);\n label.appendChild(renderFormControl(field, value, hcm));\n if (field.inputType === \"reference\" && field.reference?.model) {\n const hint = document.createElement(\"small\");\n hint.className = \"mini-workbench__field-hint\";\n hint.textContent = \"关联模型:\" + field.reference.model + \",填写记录 ID\";\n label.appendChild(hint);\n }\n fieldBox.appendChild(label);\n }\n};\nconst readFormValues = (container) => {\n const values = {};\n for (const input of container.querySelectorAll(\"[data-form-key]\")) {\n const key = input.dataset.formKey;\n if (input.type === \"checkbox\") {\n values[key] = input.checked;\n continue;\n }\n const value = input.value?.trim();\n if (value === \"\") continue;\n values[key] = value;\n }\n return values;\n};\nconst renderList = (container, hcm) => {\n const list = container.querySelector('[data-field=\"record-list\"]');\n if (!list) return;\n list.innerHTML = \"\";\n for (const item of state.records) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.dataset.action = \"open-record\";\n button.dataset.id = textOf(item.id, \"\");\n button.textContent = recordName(item);\n list.appendChild(button);\n }\n if (!state.records.length) list.textContent = \"暂无\" + MODEL_LABEL;\n if (hcm.router.current().path === \"/\") hcm.router.replace(ROUTE_BASE);\n};\nconst loadRecords = async (container, hcm) => {\n setStatus(container, \"加载中\");\n showError(container, null);\n const result = await hcm.query({ model: MODEL, page: 0, size: 50 });\n state.records = result.rows;\n renderList(container, hcm);\n setStatus(container, \"已更新\");\n};\nconst openEditor = async (container, hcm, id) => {\n state.activeId = id;\n state.mode = \"edit\";\n setEditorVisible(container, true);\n setStatus(container, \"读取详情\");\n showError(container, null);\n const record = await hcm.get({ model: MODEL, id });\n setText(container, '[data-field=\"editor-title\"]', recordName(record));\n await renderForm(container, hcm, record);\n setDeleteVisible(container, true);\n setStatus(container, \"可编辑\");\n};\nconst openNew = async (container, hcm) => {\n state.activeId = \"\";\n state.mode = \"new\";\n setEditorVisible(container, true);\n showError(container, null);\n setText(container, '[data-field=\"editor-title\"]', \"新建\" + MODEL_LABEL);\n await renderForm(container, hcm, formDefaults());\n setDeleteVisible(container, false);\n setStatus(container, \"可创建\");\n};\nconst renderRoute = async (container, hcm) => {\n if (hcm.router.match(ROUTE_BASE + \"/new\")) {\n await openNew(container, hcm);\n return;\n }\n const route = hcm.router.match(ROUTE_BASE + \"/:id\");\n if (route) {\n await openEditor(container, hcm, route.params.id);\n return;\n }\n state.activeId = \"\";\n state.mode = \"list\";\n setEditorVisible(container, false);\n setText(container, '[data-field=\"editor-title\"]', \"选择\" + MODEL_LABEL);\n const fieldBox = container.querySelector('[data-field=\"editor-fields\"]');\n if (fieldBox) fieldBox.innerHTML = \"\";\n setDeleteVisible(container, false);\n renderList(container, hcm);\n};\nconst saveActive = async (container, hcm) => {\n if (state.mode !== \"new\" && !state.activeId) return;\n const meta = await ensureMeta(hcm);\n const validation = hcm.form.validate(meta, readFormValues(container), {\n ...formOptions(),\n defaults: state.mode === \"new\" ? formDefaults() : {},\n });\n if (!validation.valid) {\n showError(container, { code: \"FORM_INVALID\", message: validation.errors.map((item) => item.message).join(\"\\\\n\") });\n return;\n }\n setStatus(container, state.mode === \"new\" ? \"创建中\" : \"保存中\");\n showError(container, null);\n if (state.mode === \"new\") {\n const created = await hcm.create({ model: MODEL, data: validation.payload });\n await loadRecords(container, hcm);\n hcm.router.push(created?.id ? ROUTE_BASE + \"/\" + encodeURIComponent(created.id) : ROUTE_BASE);\n setStatus(container, \"已创建\");\n return;\n }\n await hcm.update({ model: MODEL, id: state.activeId, data: validation.payload });\n await loadRecords(container, hcm);\n hcm.router.push(ROUTE_BASE + \"/\" + encodeURIComponent(state.activeId));\n setStatus(container, \"已保存\");\n};\nconst deleteActive = async (container, hcm) => {\n if (!state.activeId) return;\n const title = container.querySelector('[data-field=\"editor-title\"]')?.textContent || state.activeId;\n const confirmed = typeof window === \"undefined\" || window.confirm(\"确定删除\" + MODEL_LABEL + \" \" + title + \"?\");\n if (!confirmed) return;\n setStatus(container, \"删除中\");\n showError(container, null);\n await hcm.remove({ model: MODEL, id: state.activeId });\n state.activeId = \"\";\n state.mode = \"list\";\n await loadRecords(container, hcm);\n hcm.router.push(ROUTE_BASE);\n setStatus(container, \"已删除\");\n};\nexport async function mount(container, hcm) {\n setText(container, '[data-field=\"tenant-id\"]', hcm.context.tenantId);\n const onClick = (event) => {\n const target = event.target instanceof Element ? event.target.closest(\"[data-action]\") : null;\n if (!target) return;\n if (target.dataset.action === \"refresh\") void loadRecords(container, hcm).catch((error) => showError(container, error));\n if (target.dataset.action === \"new-record\") hcm.router.push(ROUTE_BASE + \"/new\");\n if (target.dataset.action === \"open-record\" && target.dataset.id) hcm.router.push(ROUTE_BASE + \"/\" + encodeURIComponent(target.dataset.id));\n if (target.dataset.action === \"back\") hcm.router.push(ROUTE_BASE);\n if (target.dataset.action === \"delete-record\") {\n void deleteActive(container, hcm).catch((error) => {\n setStatus(container, \"删除失败\");\n showError(container, error);\n });\n }\n };\n const onSubmit = (event) => {\n event.preventDefault();\n void saveActive(container, hcm).catch((error) => {\n setStatus(container, \"保存失败\");\n showError(container, error);\n });\n };\n container.addEventListener(\"click\", onClick);\n container.querySelector('[data-field=\"editor-form\"]')?.addEventListener(\"submit\", onSubmit);\n state.unsubscribe = hcm.router.subscribe(() => void renderRoute(container, hcm).catch((error) => showError(container, error)));\n await ensureMeta(hcm);\n await loadRecords(container, hcm);\n await renderRoute(container, hcm);\n container.__miniCleanup = () => {\n container.removeEventListener(\"click\", onClick);\n container.querySelector('[data-field=\"editor-form\"]')?.removeEventListener(\"submit\", onSubmit);\n state.unsubscribe?.();\n };\n}\n\nexport function unmount(container) {\n container.__miniCleanup?.();\n container.innerHTML = \"\";\n}\n`;\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 模型缓存运维(两级缓存:Caffeine 本地 + Redis 远程)。\n *\n * 后端契约(CommonModelQueryCacheController):\n * - `GET /api/models/{model}/cache` 统计\n * - `DELETE /api/models/{model}/cache` 清全部实体缓存\n * - `DELETE /api/models/{model}/cache/{id}` 清单条\n * - `DELETE /api/models/{model}/meta/cache` 清元数据缓存(Code 级 YAML + Action 定义)\n *\n * 🔴 缓存必须走 API 管理,不直接操作 Redis —— 保证一致性、审计与业务规则生效。\n */\n\nexport interface CacheStats {\n modelKey: string;\n [k: string]: unknown;\n}\n\nexport interface CacheClearResult {\n modelKey: string;\n clearedCount?: number;\n message?: string;\n [k: string]: unknown;\n}\n\nexport async function getCacheStats(http: AxiosInstance, model: string): Promise<CacheStats> {\n const resp = await http.get(`/api/models/${model}/cache`);\n return (resp.data?.data ?? resp.data) as CacheStats;\n}\n\nexport async function clearModelCache(\n http: AxiosInstance,\n model: string,\n id?: string,\n): Promise<CacheClearResult> {\n const url = id ? `/api/models/${model}/cache/${id}` : `/api/models/${model}/cache`;\n const resp = await http.delete(url);\n return (resp.data?.data ?? resp.data) as CacheClearResult;\n}\n\nexport interface ClearMetaCacheOpts {\n /** 元数据类型(list / info / view / filter / page…),省略 = 全清 */\n type?: string;\n /** 状态变体(如 self),配合 type 使用 */\n state?: string;\n}\n\nexport async function clearMetaCache(\n http: AxiosInstance,\n model: string,\n opts: ClearMetaCacheOpts = {},\n): Promise<CacheClearResult> {\n const params: Record<string, string> = {};\n if (opts.type) params.type = opts.type;\n if (opts.state) params.state = opts.state;\n const resp = await http.delete(`/api/models/${model}/meta/cache`, { params });\n return (resp.data?.data ?? resp.data) as CacheClearResult;\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 租户开通(bootstrap)—— 交付旅程第一步。\n *\n * 后端契约(TenantBootstrapController):\n * - `POST /api/bootstrap/tenant`,header `X-Bootstrap-Key: <secret>`\n * - body `{ tenantId, adminUsername, adminPassword, tenantName? }`\n *\n * 🔴 该端点不走普通 Bearer 认证,用独立的 bootstrap key —— 它要在「还没有任何\n * 租户和用户」的时刻可用。key 由部署方持有,绝不写进 skill 正文或提交进仓库。\n */\n\nexport interface BootstrapTenantRequest {\n tenantId: string;\n adminUsername: string;\n adminPassword: string;\n tenantName?: string;\n}\n\nexport interface BootstrapReconcileResult {\n component?: string;\n version?: number;\n status?: string;\n [k: string]: unknown;\n}\n\nexport interface BootstrapTenantResult {\n success: boolean;\n tenantId: string;\n adminUserId?: string;\n message?: string;\n results?: BootstrapReconcileResult[];\n}\n\nexport async function bootstrapTenant(\n http: AxiosInstance,\n bootstrapKey: string,\n req: BootstrapTenantRequest,\n): Promise<BootstrapTenantResult> {\n const resp = await http.post('/api/bootstrap/tenant', req, {\n headers: { 'X-Bootstrap-Key': bootstrapKey },\n });\n return (resp.data?.data ?? resp.data) as BootstrapTenantResult;\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 租户元数据读写(TenantMetaController,`/api/tenant-meta`)。\n *\n * 统一走 query-param 形态(`?path=`)而非路径通配 `/meta/**`:meta 路径含斜杠与点,\n * query param 避免 URL 段解析歧义与路径穿越面。\n *\n * 后端契约:\n * - `GET /api/tenant-meta/list?keyword=&type=&page=&pageSize=`\n * - `GET /api/tenant-meta/meta?path=<metaName>`\n * - `PUT /api/tenant-meta/meta?path=<metaName>` body `{ content: \"<原文>\" }`\n * - `DELETE /api/tenant-meta/meta?path=<metaName>`\n *\n * 🔴 写入需 config-admin 身份,且 source 必须可写(租户开发空间绑定态)——\n * 后端 `assertConfigAdmin` / `assertWritableSource` 会拒绝越权与只读源。\n */\n\nexport interface TenantMetaEntry {\n metaName?: string;\n name?: string;\n type?: string;\n lastModified?: string;\n size?: number;\n [k: string]: unknown;\n}\n\nexport interface ListTenantMetaOpts {\n keyword?: string;\n /** yml / properties / all(默认 all) */\n type?: string;\n page?: number;\n pageSize?: number;\n}\n\nexport async function listTenantMeta(\n http: AxiosInstance,\n opts: ListTenantMetaOpts = {},\n): Promise<{ items: TenantMetaEntry[]; total?: number; raw: unknown }> {\n const resp = await http.get('/api/tenant-meta/list', { params: opts });\n const payload = resp.data?.data ?? resp.data;\n const items = (payload?.items ?? payload?.list ?? payload?.records ?? []) as TenantMetaEntry[];\n return { items, total: payload?.total, raw: payload };\n}\n\n/** 读单份 meta 原文。返回 `content` 字符串(后端包在信封里时自动解包)。 */\nexport async function getTenantMeta(http: AxiosInstance, path: string): Promise<string> {\n const resp = await http.get('/api/tenant-meta/meta', { params: { path } });\n const payload = resp.data?.data ?? resp.data;\n if (typeof payload === 'string') return payload;\n return String(payload?.content ?? '');\n}\n\nexport interface SaveMetaResult {\n success?: boolean;\n message?: string;\n [k: string]: unknown;\n}\n\nexport async function saveTenantMeta(\n http: AxiosInstance,\n path: string,\n content: string,\n): Promise<SaveMetaResult> {\n const resp = await http.put('/api/tenant-meta/meta', { content }, { params: { path } });\n return (resp.data?.data ?? resp.data) as SaveMetaResult;\n}\n\nexport async function deleteTenantMeta(\n http: AxiosInstance,\n path: string,\n): Promise<SaveMetaResult> {\n const resp = await http.delete('/api/tenant-meta/meta', { params: { path } });\n return (resp.data?.data ?? resp.data) as SaveMetaResult;\n}\n","import type { AxiosInstance } from 'axios';\nimport yaml, { JSON_SCHEMA } from 'js-yaml';\nimport { CliError } from './errors/cli-error';\nimport { CliErrorCode } from './errors/codes';\n\n/**\n * 睿戎 Chiron 交付技能库客户端。\n *\n * <p>🔴 这些端点**免认证**——交付顾问接手的是「还没有租户、还没有账号」的空环境,\n * 必须先装技能才能建租户。所以本模块只需要 endpoint,不碰 identity/token。\n *\n * 后端契约(ChironController):\n * - `GET {base}/skills.json` 目录 manifest\n * - `GET {base}/skills/{id}.md` 技能原文\n * - `GET {base}/skills/{id}/references/{path}` 技能自带的参考文件\n * - `GET {base}/skills/{id}/files?path=...` 完整 Skill 目录中的原始文件\n *\n * 🔴 `{base}` 是 `'/api'` 或 `''`,由 {@link resolveChironBase} 探出来。真实部署只把\n * `/api/**` 路由给后端,根路径归前端 SPA——`GET /skills.json` 在客户环境上拿回来的是\n * 一段 HTML。而老版本后端只有根路径。两种都要能连上,所以先探再用,不写死。\n */\n\nexport interface ChironSkillFile {\n path: string;\n bytes: number;\n sha256: string;\n}\n\nexport interface ChironSkill {\n id: string;\n title: string;\n category: string;\n /** cli = 动作面已全改写为 hcm 命令;curl = 仍为原始 HTTP 形态 */\n actionSurface: 'cli' | 'curl';\n description: string;\n /** 交付生命周期阶段(多值)——一个技能天然跨阶段 */\n stages: string[];\n /** 安装本技能前必须同时安装的其他 Chiron 技能 id;旧服务端缺省时视为空。 */\n requires?: string[];\n bytes: number;\n /** 新服务端提供完整 Skill 目录;旧服务端缺省时只消费 SKILL.md。 */\n files?: ChironSkillFile[];\n markdownUrl: string;\n /**\n * 技能自带的参考文件相对路径(相对 `<skill>/references/`)。SKILL.md 正文用\n * `[x](references/x.md)` 引它们——不跟着装下来就是一堆死链。旧服务端缺省时视为空。\n */\n references?: string[];\n}\n\nexport interface ChironStage {\n key: string;\n name: string;\n description: string;\n /** 该阶段下的技能数。0 = 交付路径上的覆盖缺口 */\n skillCount: number;\n}\n\nexport interface ChironCategory {\n key: string;\n name: string;\n description: string;\n skills: ChironSkill[];\n}\n\nexport interface ChironManifest {\n name: string;\n title: string;\n tagline: string;\n skillCount: number;\n stages: ChironStage[];\n categories: ChironCategory[];\n}\n\n/** 技能库端点的前缀候选,按优先级排列。`/api` 在前:它是真实部署下唯一到得了后端的那条。 */\nconst CHIRON_BASE_CANDIDATES = ['/api', ''] as const;\n\nfunction looksLikeManifest(data: unknown): data is ChironManifest {\n if (!data || typeof data !== 'object') return false;\n const m = data as Record<string, unknown>;\n return m.name === 'chiron' && Array.isArray(m.categories);\n}\n\n/**\n * 探出本环境技能库的前缀。\n *\n * 为什么不能写死任何一个:\n * - 真实部署(反代把 `/api/**` 给后端、其余给前端)下根路径返回的是前端 SPA 的 HTML,\n * HTTP 200 但根本不是 manifest —— 所以判据必须看**内容**,不能看状态码;\n * - 还没升级到带 `/api` 别名那一版的后端,只有根路径可用。\n *\n * 两条都探不到时抛出可执行的错误,而不是把一段 HTML 当目录解析出满屏乱码。\n */\nexport async function resolveChironBase(http: AxiosInstance): Promise<string> {\n const tried: string[] = [];\n for (const base of CHIRON_BASE_CANDIDATES) {\n const url = `${base}/skills.json`;\n tried.push(url);\n try {\n const resp = await http.get(url);\n const data = resp.data?.data ?? resp.data;\n if (looksLikeManifest(data)) return base;\n } catch {\n // 401 / 404 / 网络错误都只说明这条前缀不通,继续探下一条\n }\n }\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message:\n `这个环境上找不到睿戎技能库(试过 ${tried.join(' 和 ')})。`\n + '常见原因:① 后端版本还没有 /api/skills 端点,升级后端即可;'\n + '② endpoint 填的是前端地址且未反代到后端;③ 地址或网络不通。',\n });\n}\n\nexport async function fetchSkillCatalog(http: AxiosInstance, base: string): Promise<ChironManifest> {\n const resp = await http.get(`${base}/skills.json`);\n return (resp.data?.data ?? resp.data) as ChironManifest;\n}\n\nexport async function fetchSkillMarkdown(http: AxiosInstance, id: string, base: string): Promise<string> {\n const resp = await http.get(`${base}/skills/${encodeURIComponent(id)}.md`, {\n responseType: 'text',\n transformResponse: [(d) => d],\n });\n return String(resp.data ?? '');\n}\n\n/** 取完整 Skill 目录中的任意原始文件;`relativePath` 始终按 query 参数传递。 */\nexport async function fetchSkillFile(\n http: AxiosInstance,\n id: string,\n relativePath: string,\n base: string,\n): Promise<Buffer> {\n assertSafeSkillId(id);\n assertSafeSkillFilePath(relativePath);\n const resp = await http.get(`${base}/skills/${encodeURIComponent(id)}/files`, {\n params: { path: relativePath },\n responseType: 'arraybuffer',\n transformResponse: [(data) => data],\n });\n return Buffer.from(resp.data ?? []);\n}\n\n/** 取技能自带的参考文件原文。`relativePath` 相对 `<skill>/references/`。 */\nexport async function fetchSkillReference(\n http: AxiosInstance,\n id: string,\n relativePath: string,\n base: string,\n): Promise<string> {\n assertSafeReferencePath(relativePath);\n const encoded = relativePath.split('/').map(encodeURIComponent).join('/');\n const resp = await http.get(`${base}/skills/${encodeURIComponent(id)}/references/${encoded}`, {\n responseType: 'text',\n transformResponse: [(d) => d],\n });\n return String(resp.data ?? '');\n}\n\n/**\n * 参考文件相对路径约束——与后端 `ChironCatalogService.SAFE_REFERENCE_PATH` 同形。\n *\n * 🔴 服务端返回什么,本地就按什么建目录,所以这里是**写文件前**的闸:`..` 段会让\n * 安装写出技能目录之外。不要因为「服务端已经校验过」就省掉——CLI 可以指向任何 endpoint。\n */\nexport function assertSafeReferencePath(relativePath: string): void {\n const ok = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,63}(\\/[A-Za-z0-9_][A-Za-z0-9._-]{0,63}){0,2}$/\n .test(relativePath);\n if (!ok) {\n throw invalid(`参考文件路径不合法:${JSON.stringify(relativePath)}`);\n }\n}\n\n/** 完整 Skill 目录相对路径约束;允许有限深度之外的目录,但拒绝任何穿越和反斜线。 */\nexport function assertSafeSkillFilePath(relativePath: string): void {\n const segments = relativePath?.split('/') ?? [];\n if (\n !relativePath ||\n relativePath.length > 512 ||\n relativePath.startsWith('/') ||\n relativePath.includes('\\\\') ||\n segments.some(\n (segment) =>\n !segment ||\n segment === '.' ||\n segment === '..' ||\n !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(segment),\n )\n ) {\n throw invalid(`非法 Skill 文件路径: \"${relativePath}\"`);\n }\n}\n\n/** 归一 references 字段:旧服务端缺省时视为空,逐条过白名单。 */\nexport function normalizeReferences(raw: unknown, skillId: string): string[] {\n if (raw === undefined || raw === null) return [];\n if (!Array.isArray(raw)) throw invalid(`技能 ${skillId} 的 references 只能是字符串数组`);\n const out: string[] = [];\n for (const value of raw) {\n if (typeof value !== 'string' || !value.trim()) {\n throw invalid(`技能 ${skillId} 的 references 只能是非空字符串数组`);\n }\n const rel = value.trim();\n assertSafeReferencePath(rel);\n if (!out.includes(rel)) out.push(rel);\n }\n return out;\n}\n\nexport function flattenSkills(manifest: ChironManifest): ChironSkill[] {\n return (manifest.categories ?? []).flatMap((c) => c.skills ?? []);\n}\n\n/**\n * 计算技能安装闭包,返回稳定的「依赖在前、目标在后」顺序。\n *\n * 这是纯函数:远程安装与测试共用,不在这里发请求或写文件。旧目录没有 requires 时\n * 自然退化为只安装目标技能。\n */\nexport function resolveSkillInstallOrder(manifest: ChironManifest, targetId: string): ChironSkill[] {\n const byId = new Map<string, ChironSkill>();\n for (const skill of flattenSkills(manifest)) {\n if (byId.has(skill.id)) {\n throw invalid(`技能目录存在重复 id: ${skill.id}`);\n }\n byId.set(skill.id, skill);\n }\n if (!byId.has(targetId)) {\n throw invalid(`技能目录中不存在 ${targetId}`);\n }\n\n const state = new Map<string, 'visiting' | 'done'>();\n const path: string[] = [];\n const order: ChironSkill[] = [];\n\n const visit = (id: string, requiredBy?: string): void => {\n const current = state.get(id);\n if (current === 'done') return;\n if (current === 'visiting') {\n const cycleStart = path.indexOf(id);\n const cycle = [...path.slice(Math.max(0, cycleStart)), id];\n throw invalid(`技能依赖存在环: ${cycle.join(' -> ')}`);\n }\n\n const skill = byId.get(id);\n if (!skill) {\n throw invalid(`技能 ${requiredBy ?? targetId} 依赖不存在: ${id}`);\n }\n assertSafeSkillId(skill.id);\n state.set(id, 'visiting');\n path.push(id);\n for (const required of normalizeRequirements(skill.requires, skill.id)) {\n visit(required, skill.id);\n }\n path.pop();\n state.set(id, 'done');\n order.push(skill);\n };\n\n visit(targetId);\n return order;\n}\n\n/**\n * 全目录的安装顺序:逐个技能求依赖闭包后合并去重,依赖仍排在被依赖者之前。\n *\n * `install --all` 用它。顾问不必知道谁依赖谁,也不该为了装全而写 shell 循环——\n * 那个循环在 zsh 下 `for id in $(...)` 不分词,会一个都不装却退出码 0。\n */\nexport function resolveAllSkillsInstallOrder(manifest: ChironManifest): ChironSkill[] {\n const order = new Map<string, ChironSkill>();\n const all = flattenSkills(manifest).map((skill) => skill.id).sort();\n for (const id of all) {\n for (const skill of resolveSkillInstallOrder(manifest, id)) {\n if (!order.has(skill.id)) order.set(skill.id, skill);\n }\n }\n return [...order.values()];\n}\n\n/** 读 SKILL.md 的 frontmatter;没有 frontmatter 返回空对象,YAML 非法则报错。 */\nexport function parseSkillFrontmatter(markdown: string, skillId: string): Record<string, unknown> {\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/.exec(markdown);\n if (!match) return {};\n\n let frontmatter: unknown;\n try {\n frontmatter = yaml.load(match[1] ?? '', { schema: JSON_SCHEMA });\n } catch (cause) {\n throw new CliError({\n code: CliErrorCode.INVALID_ARGUMENT,\n message: `技能 ${skillId} 的 frontmatter 不是合法 YAML`,\n cause,\n });\n }\n if (!frontmatter || typeof frontmatter !== 'object' || Array.isArray(frontmatter)) return {};\n return frontmatter as Record<string, unknown>;\n}\n\n/** 从一份 SKILL.md 读取本地安装所需的 requires,不接受正文中的近似语法。 */\nexport function parseSkillRequirements(markdown: string, skillId: string): string[] {\n return normalizeRequirements(parseSkillFrontmatter(markdown, skillId).requires, skillId);\n}\n\n/** Chiron 与公开端点共用的小写安全 id 约束。 */\nexport function assertSafeSkillId(id: string): void {\n if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(id)) {\n throw invalid(`技能 id 只能是小写字母/数字/连字符,收到 \"${id}\"`);\n }\n}\n\nfunction normalizeRequirements(raw: unknown, skillId: string): string[] {\n if (raw === undefined || raw === null) return [];\n const values = Array.isArray(raw) ? raw : [raw];\n const normalized: string[] = [];\n for (const value of values) {\n if (typeof value !== 'string' || !value.trim()) {\n throw invalid(`技能 ${skillId} 的 requires 只能是非空技能 id 数组`);\n }\n const id = value.trim();\n assertSafeSkillId(id);\n if (!normalized.includes(id)) normalized.push(id);\n }\n return normalized;\n}\n\nfunction invalid(message: string): CliError {\n return new CliError({ code: CliErrorCode.INVALID_ARGUMENT, message });\n}\n\n/** 按交付生命周期阶段筛选。 */\nexport function filterByStage(skills: ChironSkill[], stage?: string): ChironSkill[] {\n if (!stage) return skills;\n return skills.filter((s) => (s.stages ?? []).includes(stage));\n}\n\n/** 关键词匹配 id / 标题 / 描述 / 分类,全部小写包含。 */\nexport function matchSkills(skills: ChironSkill[], keyword?: string): ChironSkill[] {\n if (!keyword) return skills;\n const k = keyword.toLowerCase();\n return skills.filter((s) =>\n [s.id, s.title, s.description, s.category].some((f) => String(f ?? '').toLowerCase().includes(k)),\n );\n}\n","import type { AxiosInstance } from 'axios';\n\n/**\n * 租户系统参数(配置中心)。\n *\n * <p>后端契约(TenantSettingDomainController):\n * <ul>\n * <li>`GET /api/tenant-settings/domains/{domain}` 读某域(定义 + 租户覆盖值合并,缺失回退默认值)</li>\n * <li>`PATCH /api/tenant-settings/domains/{domain}` 写,body `{items:[{namespace,settingKey,value}]}`</li>\n * <li>`DELETE /api/tenant-settings/domains/{domain}/items/{ns}/{key}?revision=N` 清除租户覆盖</li>\n * </ul>\n *\n * <p>🔴 **不要走标准 Model 的 PUT 改这些值**:那条路直接写库,绕过定义校验、\n * 乐观锁与历史留痕——改坏 namespace/valueType 会让记录不再匹配任何定义。\n * 后端已从该 Model 的权限里移除 update action,这里也只暴露上面三条受控路径。\n */\n\nexport interface SettingItem {\n namespace: string;\n settingKey: string;\n value?: unknown;\n valueType?: string;\n defaultValue?: unknown;\n revision?: number;\n label?: string;\n [k: string]: unknown;\n}\n\nexport async function getSettingDomain(\n http: AxiosInstance,\n domain: string,\n): Promise<{ items: SettingItem[]; raw: unknown }> {\n const resp = await http.get(`/api/tenant-settings/domains/${encodeURIComponent(domain)}`);\n const payload = resp.data?.data ?? resp.data;\n const items = (payload?.items ?? payload ?? []) as SettingItem[];\n return { items: Array.isArray(items) ? items : [], raw: payload };\n}\n\nexport interface SettingWrite {\n namespace: string;\n settingKey: string;\n value: unknown;\n}\n\nexport async function patchSettingDomain(\n http: AxiosInstance,\n domain: string,\n items: SettingWrite[],\n): Promise<unknown> {\n const resp = await http.patch(`/api/tenant-settings/domains/${encodeURIComponent(domain)}`, { items });\n return resp.data?.data ?? resp.data;\n}\n\nexport async function resetSettingItem(\n http: AxiosInstance,\n domain: string,\n namespace: string,\n settingKey: string,\n revision?: number,\n): Promise<unknown> {\n const url =\n `/api/tenant-settings/domains/${encodeURIComponent(domain)}` +\n `/items/${encodeURIComponent(namespace)}/${encodeURIComponent(settingKey)}`;\n const resp = await http.delete(url, {\n params: revision === undefined ? {} : { revision },\n });\n return resp.data?.data ?? resp.data;\n}\n\n/**\n * 解析 `namespace.settingKey=value` 形态的 --set 参数。\n *\n * settingKey 取最后一段,其余是 namespace —— namespace 本身含点\n * (如 `security.password`),所以从右边切,不能 split('.') 取前两段。\n */\nexport function parseSettingAssignment(raw: string): SettingWrite {\n const eq = raw.indexOf('=');\n if (eq <= 0) throw new Error(`--set 需要 namespace.key=value 形态,收到 \"${raw}\"`);\n const path = raw.slice(0, eq).trim();\n const value = raw.slice(eq + 1);\n const dot = path.lastIndexOf('.');\n if (dot <= 0) throw new Error(`--set 的键需含 namespace,如 security.password.passwordPolicy=complex,收到 \"${path}\"`);\n return { namespace: path.slice(0, dot), settingKey: path.slice(dot + 1), value };\n}\n","/**\n * FDE 侦察产物的目录布局与路径遏制。\n *\n * 产物按**爬取身份**分叉:`<root>/recon/<tenantId>/<sourceKey>/…`。\n * 同一个租户、同一条命令,两个 FDE 顾问绑在不同 workspace 上时看到的元数据是不同的——\n * 把两份产物混在一个目录里,后来者不是「挤在一起」,而是**静默覆盖**前一个人的\n * `models.json` / `meta/*.json`,而 `fingerprint.json` 里只剩最后写入者的 `extensionSource`。\n *\n * 移植自 `hcm_nx_core` 的 `tools/fde-kit/hcmnx_kit/layout.py`(ADR-319 刀 6b)。\n * 行为基线是 `docs/superpowers/specs/2026-08-28-fde-kit-behavior-contract.md` §1.3 的 19 条 P。\n */\nimport { createHash } from 'node:crypto';\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport * as path from 'node:path';\n\nconst UNSAFE = /[^A-Za-z0-9._-]/g;\n\n/** 把不安全字符换成下划线。**不保证单射**,只用于人眼可读的那一半。 */\nfunction slug(value: string): string {\n return (value || 'unknown').replace(UNSAFE, '_');\n}\n\n/**\n * sourceKey 实测形如 `ws:poc-cnpc`;冒号等字符不能直接进路径。\n *\n * 🔴 纯字符替换做不到单射(`ws:alice` 与 `ws_alice` 会撞进同一个目录),而不同 sourceKey\n * 的产物不可互换——撞目录不是「挤在一起」,是后跑的 FDE 静默覆盖前一个 FDE 的\n * models.json / meta/*.json。所以在可读 slug 后面缀上**原始键**的短摘要:\n * slug 用来给人看,后缀用来保证身份。\n */\nexport function safeSegment(value: string): string {\n const raw = value || 'unknown';\n const digest = createHash('sha256').update(raw, 'utf8').digest('hex').slice(0, 10);\n return `${slug(raw)}-${digest}`;\n}\n\n/**\n * 租户段的归一。\n *\n * 🔴 **它就是 `safeSegment`,不是另一个归一函数。** 保留独立函数名只为让调用点读起来\n * 有语义(也给命令层一个公开入口,两端才不会各自拼路径),实现必须**原样委派**,\n * 绝不允许两个归一算法并存。\n *\n * ⚠️ 不要改成「校验 tenantId 字符集、非法即拒」:那会在某个 tenantId 真含特殊字符的\n * 环境上**硬拒一个合法环境**,把可用性押在我们对 tenantId 取值范围的猜测上。\n * 摘要后缀不拒绝任何输入,且无条件单射。\n */\nexport function tenantSegment(value: string): string {\n return safeSegment(value);\n}\n\n/**\n * `p` 解析后是否落在 `container` 之内(含相等)。\n *\n * 🔴 **判分量,不判前缀**。写成 `rel.startsWith('..')` 会误杀两类合法路径,\n * 且两类都真实存在于本模块:\n * · `..foo.json` / `...json` —— 合法文件名(契约 §1.3「仅以点开头的文件名不被误杀」);\n * · `..-<摘要>/` —— `safeSegment('..')` 的产物,租户 id 恰好是 `..` 时的正常目录名。\n * 移植时这一格栽过一次:三条用例同时红,根因都在这个前缀判据上。\n */\nfunction isInside(p: string, container: string): boolean {\n const rel = path.relative(container, p);\n if (rel === '') return true;\n if (path.isAbsolute(rel)) return false;\n return !rel.split(path.sep).includes('..');\n}\n\n/**\n * 判 `rel` 里有没有 `..` **分量**。\n *\n * 🔴 判分量而不是字符串 `includes`:`..foo.json` / `...json` 是合法文件名,\n * 字符串判据会把它们误杀。\n *\n * 🔴 `/` **和** `\\` 都当分隔符:`valuedomain/..\\evil.json`、`valuedomain\\..\\evil.json`\n * 这两种形态也必须判得出 `..` 分量。只按 POSIX 分隔符切对它们恒返 false,是个盲区。\n * 收益不是「支持 Windows」,是**多认一种分隔符、少一个盲区**。\n */\nfunction hasDotDotComponent(rel: string): boolean {\n return rel.split(/[/\\\\]/).includes('..');\n}\n\nexport class ReconLayout {\n readonly root: string;\n readonly tenantId: string;\n readonly sourceKey: string;\n readonly dir: string;\n\n constructor(root: string, tenantId: string, sourceKey: string) {\n this.root = root;\n this.tenantId = String(tenantId);\n this.sourceKey = sourceKey;\n // 🔴 产物按爬取身份分叉:不同 sourceKey 的产物不可互换。\n // 两段都走 `safeSegment`——单射由摘要后缀无条件保证。\n this.dir = path.join(\n this.root,\n 'recon',\n tenantSegment(this.tenantId),\n safeSegment(sourceKey),\n );\n // 🔴 容器断言**不是装饰品**:它守的前提是「**归一函数没被改弱**」。\n // 把 `safeSegment` 退回纯字符替换,`--tenant \"..\"` 立刻让产物根跳出 recon/:\n // `<out>/recon/../active-xxx` 规范化后是 `<out>/active-xxx`。\n // `resolvePath` 的闸拦不住这一步——那道闸只比 `this.dir`,而 `this.dir` 自己已经偏了。\n const container = path.resolve(this.root, 'recon');\n if (!isInside(path.resolve(this.dir), container)) {\n throw new LayoutContainmentError(\n `租户段越界,拒绝构造产物目录:tenantId=${JSON.stringify(tenantId)}(产物根 ${container})`,\n );\n }\n }\n\n /**\n * 把相对路径解析成绝对路径,并过两道**互相印证**的遏制闸。\n *\n * 🔴 `rel` 里会嵌入**现爬的 modelKey**:`meta/<k>.model.json`、`valuedomain/<k>.json`、\n * `mapping/<k>.json`。服务端说什么,这里就拼什么。\n *\n * 实测分档(沿用 Python 侧 2026-08-16 的实跑结论):\n * `../evil` → 规范化后**仍在产物根内**,写成 `<产物根>/evil.json`。第二道闸**拦不住**。\n * `../models` → 同上,**顶掉 recon 自己的模型名册**,写入方拿到「成功」,产物里没有留痕。\n * `../../evil` → 真的逃出产物根 ⇒ 第二道闸拦得住。\n * `/etc/passwd` → 前导 `/` 被路径规范化吃掉,落在 `valuedomain/etc/passwd.json`。\n * ⇒ 真正的危害不是「逃出产物根」(那一档本来就拦得住),是「**逃出预期子目录、\n * 静默顶掉同级产物**」——`models.json` / `fingerprint.json` / `gates.json` 全在射程内。\n *\n * ⚠️ 拒绝而不是消毒:消毒会让 `meta/<modelKey>.json` 不再能按 modelKey grep 回去。\n */\n resolvePath(rel: string, createParents = true): string {\n const p = path.join(this.dir, rel);\n // 第一道闸:守「逃出预期子目录」。\n if (hasDotDotComponent(rel)) {\n throw new LayoutContainmentError(\n `产物相对路径含 \\`..\\` 分量,拒绝写入:${JSON.stringify(rel)}` +\n `(会越出预期子目录、静默顶掉同级产物;产物根 ${this.dir})`,\n );\n }\n // 第二道闸:守「逃出产物根」。\n if (!isInside(path.resolve(p), path.resolve(this.dir))) {\n throw new LayoutContainmentError(\n `产物相对路径越界,拒绝写入:${JSON.stringify(rel)}(产物根 ${this.dir})`,\n );\n }\n if (createParents) {\n // 🔴 读路径传 createParents=false:读一个不存在的产物不该顺手建出目录,\n // 否则一个空目录会冒充「这里爬过」——那是必须分开的两件事。\n mkdirSync(path.dirname(p), { recursive: true });\n }\n // 🔴 返回未 realpath 的 p:macOS 上 /var → /private/var 的符号链接会让\n // 调用方拿 this.dir 拼出来的路径断言全部错位。\n return p;\n }\n\n writeJson(rel: string, obj: unknown): string {\n const p = this.resolvePath(rel);\n // JSON.stringify 默认不转义非 ASCII——中文逐字落盘,本地 grep 才找得到。\n writeFileSync(p, JSON.stringify(obj, null, 2), 'utf8');\n return p;\n }\n\n writeJsonl(rel: string, rows: Iterable<unknown>): string {\n const p = this.resolvePath(rel);\n let body = '';\n for (const row of rows) body += `${JSON.stringify(row)}\\n`;\n writeFileSync(p, body, 'utf8');\n return p;\n }\n\n writeText(rel: string, text: string): string {\n const p = this.resolvePath(rel);\n writeFileSync(p, text, 'utf8');\n return p;\n }\n\n /**\n * 逐行读一份 JSONL 产物。\n *\n * 🔴 与 `readJson` 同走那两道越界闸——同一个收口点不能一半有闸一半没有。\n * 🔴 **跳过空行**(末尾换行是 `writeJsonl` 必然产生的),但**不跳过坏行**:\n * 坏行原样抛,因为「这一行读不出来」和「这个产物没有这一行」是两件事。\n */\n readJsonlRows(rel: string): unknown[] {\n const body = readFileSync(this.resolvePath(rel, false), 'utf8');\n const rows: unknown[] = [];\n for (const line of body.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed) rows.push(JSON.parse(trimmed));\n }\n return rows;\n }\n\n readJson(rel: string): unknown {\n // 走 resolvePath:读路径同样会嵌入现爬的 modelKey,越界读同样要拒绝。\n return JSON.parse(readFileSync(this.resolvePath(rel, false), 'utf8'));\n }\n}\n\n/**\n * 路径遏制被触发。\n *\n * 独立于 `CliError`:它是**内部不变量**被破坏,不是顾问能修的用法错误。\n * 命令层负责把它翻成一句人话 + 退出码,见 `packages/cli/src/commands/delivery-doctor.ts`。\n */\nexport class LayoutContainmentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LayoutContainmentError';\n }\n}\n","/**\n * 漂移握手四元组。\n *\n * 回答的是一个问题:**「我手上这份基线,还配得上眼前这个环境吗?」**\n *\n * 三个分量作门(`GATE_COMPONENTS`),第四个(backendVersion)只留档——实测 on-prem 恒返\n * \"unknown\",拿它作门只会得到一个恒等式。\n *\n * 移植自 `hcm_nx_core` 的 `tools/fde-kit/hcmnx_kit/fingerprint.py`(ADR-319 刀 6b)。\n * 行为基线是行为契约清单 §1.2 的 30 条 P。\n */\nimport { createHash } from 'node:crypto';\nimport type { Outcome, ReconProbe } from './probe';\n\n/** 🔴 只有这三个分量作门。backendVersion 仅记录——实测它在 on-prem 恒返 \"unknown\"。 */\nexport const GATE_COMPONENTS = [\n 'tenantMetaManifestDigest',\n 'extensionSource',\n 'modelListHash',\n] as const;\n\nexport const BLIND_SPOTS =\n '产品升级:manifest 只读租户 workspace 的 metas/ 目录,' +\n '产品升级改的是安装包内 _metas/,四元组不会变——该场景的防线只有行为探针一条。' +\n ' | 同字节数内容替换:清单只给 name/size/lastModified,' +\n '把标签「部门」改成「机构」在 UTF-8 下恰好都是 6 字节,摘要不变。' +\n '⇒ 字段名叫 ManifestDigest 而非 Fingerprint,名字必须说实话。';\n\nexport const MODELS_PATH = '/api/system/models';\nexport const MANIFEST_PATH = '/api/tenant-meta/list';\nexport const VERSION_PATH = '/api/version/backend';\nexport const META_PATH = '/api/models/{model}/meta';\n\nexport const EXTENSION_SOURCE_HEADER = 'x-hcm-extension-source';\n\n/**\n * 探针上限。取不到就取不到,**不许**为了拿到一个值把 761 个模型全试一遍——\n * 那会让 doctor 从「几秒」变成「几分钟」,而 doctor 存在的意义就是**快速**告诉顾问他站在哪儿。\n *\n * 🔴 为什么是 50 而不是 20:「20 够不够」这个问题,原工位实测第 1 个 key 就命中,\n * 但**这不能证明任何别的环境**;若某环境排序靠前的 key 恰好全是隐藏型(实测有 19 个),\n * 探针会空手而归并阻断门,而 key 21 可能就带着这个头。\n * ⇒ 正确的应对**不是**把 20 换成另一个同样证明不了的数字,而是:\n * ① 放宽到显然够用又不至于拖慢 doctor(命中即停,坏情况 50 × ~50ms ≈ 2.5s);\n * ② 把失败讲清楚(见 attempted/answered)。判据从「猜一个够用的数」换成「失败时说得出为什么」。\n */\nexport const MAX_SOURCE_PROBES = 50;\n\n/**\n * 本模块的全部前提是「打一个不认识的环境」。一个把 totalPages 报成 2**31 的\n * 服务端会让顾问的笔记本原地转圈。200 页 × 200 行 = 4 万行,远超真实 workspace。\n */\nexport const MAX_MANIFEST_PAGES = 200;\n\n/**\n * 🔴 只有**门相关**端点的取数失败才阻断。backendVersion 是明文钉死的「仅记录、不作门」\n * 分量——让它的取数失败从 incomplete: 侧门变成门,等于在任何缺 `/api/version/backend`\n * 的老构建上把门恒推成「不匹配」,而门被常态化绕过(`--force-stale`)正是要防的事。\n */\nconst GATE_SOURCES = [MODELS_PATH, MANIFEST_PATH, META_PATH];\n\nexport interface ModelEntry {\n modelKey?: string;\n type?: string;\n [k: string]: unknown;\n}\n\n/**\n * `/api/system/models` 的响应体 → **可用的**模型条目。\n *\n * 🔴 **这是唯一一道门**。原先 doctor / computeFingerprint / 布局解析三个点各写各的过滤,\n * 第三个漏了——而它跑在**最前面**,客户网关一改清单形状,recon 就在第一步崩掉,\n * 而 FDE 手上没有源码、没有安装包、也没有第二份基线可对照。\n *\n * 两种敌意形状都在这里挡掉,调用方不必(也不许)自己判:\n * ① `body` 整体不是对象——服务端**声明**返 object,但网关扒掉 envelope 直接返数组是真会发生的;\n * ② `models[]` 里混进非对象元素——部分序列化失败 / 拼接出错。\n *\n * ⚠️ **降级不是静默**:这里只负责「不崩」,「有没有掉东西」由调用方各自留痕。\n */\nexport function modelEntries(body: unknown): ModelEntry[] {\n if (!isPlainObject(body)) return [];\n const raw = (body as Record<string, unknown>).models;\n if (!Array.isArray(raw)) return [];\n return raw.filter(isPlainObject) as ModelEntry[];\n}\n\n/**\n * `/api/system/models` 的响应体 → 探针用的 modelKey 列表(去空串 + 排序)。\n *\n * 🔴 空串过滤不能省:一条缺 `modelKey` 的记录会让第一发打到 `/api/models//meta`,\n * 白烧一发**有限的**探针配额。\n * 🔴 排序 = 探针确定性:同一个环境两次 recon 必须探同一个模型,否则 `extensionSourceProbe`\n * 会无端漂移,看上去像环境变了。\n */\nexport function modelKeys(body: unknown): string[] {\n return modelEntries(body)\n .map((m) => (typeof m.modelKey === 'string' ? m.modelKey : ''))\n .filter((k) => k !== '')\n .sort();\n}\n\nexport interface ProbeResult {\n sourceKey: string;\n probeModelKey: string;\n attempted: number;\n answered: number;\n}\n\n/**\n * 探 `X-HCM-Extension-Source`。\n *\n * 🔴 **这个头只挂在 `GET /api/models/{model}/meta` 上,`/api/system/models` 上没有**。\n * 读错端点的后果不是「偶尔取不到」,是**每一个真实环境上恒为 unknown**:三个门分量残掉\n * 一个 ⇒ `compareFingerprint` 恒返 `incomplete:` ⇒ 门被 `--force-stale` 常态化绕过。\n *\n * 🔴 `answered` 不是凑数的统计量,它是「unknown 到底是哪种 unknown」的唯一判据。\n * 只返回 `(sourceKey, probeModelKey)` 时,「一个模型都没答应」与「答应了但没回这个头」\n * **都**返回空串、**都**写同一条失败条目——顾问看到 `unknown` 照样不知道该查连通/权限还是查反代。\n * **一个自己都分辨不了的分辨承诺比不承诺更糟**,它让人以为这件事已经处理了。\n */\nexport async function probeExtensionSource(\n probe: ReconProbe,\n keys: string[],\n): Promise<ProbeResult> {\n let attempted = 0;\n let answered = 0;\n for (const key of [...keys].sort().slice(0, MAX_SOURCE_PROBES)) {\n attempted += 1;\n const out = await probe.get(META_PATH.replace('{model}', key));\n // meta 404 是常态(隐藏型模型,实测有 19 个),换下一个\n if (out.error !== 'ok') continue;\n answered += 1;\n const value = out.headers[EXTENSION_SOURCE_HEADER];\n if (value) return { sourceKey: value, probeModelKey: key, attempted, answered };\n }\n return { sourceKey: '', probeModelKey: '', attempted, answered };\n}\n\n/**\n * 分页取全量租户元数据清单。page 从 0 开始,分页参数名是 `pageSize`(不是 `size`)。\n *\n * 🔴 一旦某页没取回来就把端点记进 failures:清单为空(真的没有 overlay 文件)\n * 与清单没爬到(401/网关截断)会算出**同一个** sha256(''),产物里必须有一处把这两件事分开,\n * 否则「爬过且为空」和「没爬」在磁盘上无法区分。\n */\nasync function fetchManifestRows(\n probe: ReconProbe,\n failures: string[],\n): Promise<Record<string, unknown>[]> {\n const rows: Record<string, unknown>[] = [];\n let page = 0;\n const pageSize = 200;\n for (;;) {\n const out = await probe.get(MANIFEST_PATH, { page, pageSize });\n if (out.error !== 'ok' || !isPlainObject(out.body)) {\n failures.push(MANIFEST_PATH);\n break;\n }\n const body = out.body as Record<string, unknown>;\n const chunk = body.data ?? [];\n if (!Array.isArray(chunk)) {\n failures.push(MANIFEST_PATH);\n break;\n }\n rows.push(...(chunk.filter(isPlainObject) as Record<string, unknown>[]));\n const totalPages = typeof body.totalPages === 'number' ? body.totalPages : 0;\n page += 1;\n if (page >= totalPages || chunk.length === 0) break;\n if (page >= MAX_MANIFEST_PAGES) {\n // 🔴 截断必须留痕,否则又是一个「截断了但看不出来」:少了后面几页的清单,\n // 摘要照样算得出来,看上去和完整清单没两样。\n failures.push(`${MANIFEST_PATH}#page-cap`);\n break;\n }\n }\n return rows;\n}\n\nfunction manifestDigest(rows: Record<string, unknown>[]): string {\n // 对**全量清单**求摘要:集合成员变化必然改变摘要(高水位做不到这点——\n // 删掉一个非最新的文件、或恢复一个旧时间戳文件,高水位纹丝不动)。\n const lines = rows\n .map((r) => `${r.name}|${r.size}|${r.lastModified}`)\n .sort();\n return sha256(lines.join('\\n'));\n}\n\n/**\n * 产物目录要用的 `sourceKey`——`recon` 打出去的**第一个**请求序列。\n *\n * 🔴 单独一个入口,而**不是**让命令层自己 `probe.get(MODELS_PATH)` 再就地取 key:\n * `probeExtensionSource` 此前有三个调用点,其中一个(recon 的这一跳)漏了\n * 「元素可能不是对象 / body 整体可能不是对象 / modelKey 可能是空串」这三道过滤,\n * 于是客户网关一改清单形状,`recon` 就在第一步以一条裸堆栈收场 —— 而顾问手上\n * 没有源码、没有安装包、也没有第二份基线可对照。\n * 三处各修各的会再漏第四处;收进 `modelKeys()` 这**一道门**,漏不掉。\n *\n * 🔴 探不到时回 `'unknown'` 而不是抛:探不到是**可降级**的(产物目录名带 unknown,\n * 顾问一眼看得出这份基线的层次身份没确认),而抛会让整轮爬取在第一步就没了。\n */\nexport async function computeReconSourceKey(probe: ReconProbe): Promise<string> {\n const out = await probe.get(MODELS_PATH);\n const p = await probeExtensionSource(probe, modelKeys(out.body));\n return p.sourceKey || 'unknown';\n}\n\nexport interface Fingerprint {\n tenantMetaManifestDigest: string;\n manifestEntryCount: number;\n extensionSource: string;\n extensionSourceProbe: string;\n extensionSourceProbeAttempted: number;\n extensionSourceProbeAnswered: number;\n modelListHash: string;\n modelCount: number;\n backendVersion: unknown;\n capabilities: string;\n createdAt: string;\n ttlDays: number;\n coverageNotes: {\n gateComponents: string[];\n blindSpots: string;\n fetchFailures: string[];\n };\n}\n\nexport async function computeFingerprint(\n probe: ReconProbe,\n opts: { ttlDays?: number; now?: () => Date } = {},\n): Promise<Fingerprint> {\n const ttlDays = opts.ttlDays ?? 30;\n const now = opts.now ?? (() => new Date());\n const failures: string[] = [];\n\n const modelsOut = await probe.get(MODELS_PATH);\n // 🔴 body 声明是 object,服务端完全可能返回 array/string——直接取属性会在客户现场炸。\n // 判成功一律 error === 'ok',再核类型。\n const modelsOk = modelsOut.error === 'ok' && isPlainObject(modelsOut.body);\n if (!modelsOk) failures.push(MODELS_PATH);\n\n const models = modelsOk ? modelEntries(modelsOut.body) : [];\n const keys = modelsOk ? modelKeys(modelsOut.body) : [];\n\n if (modelsOut.error === 'ok' && keys.length === 0) {\n // 🔴 请求成功但**一个可用 modelKey 都没有** ⇒ modelListHash 会算出 sha256('')\n // ——一个语法完整、语义为空的值——而 fetchFailures 若保持为空,门就拿它当\n // 「爬到了,就是这个值」放行。实测该环境有 761 个模型,**零可用 key 只可能是出了事**\n // (键名读错 / 权限 / 网关改写),不可能是「这个环境真的没有模型」。\n failures.push(`${MODELS_PATH}#empty-model-list`);\n }\n\n const p = await probeExtensionSource(probe, keys);\n let sourceKey = p.sourceKey;\n if (!sourceKey) {\n // 🔴 两种 unknown 的处置**相反**,所以标记必须不同。\n // answered === 0 ⇒ 没有任何模型答应 ⇒ 查连通/权限/模型清单是不是空的,**与反代无关**;\n // answered > 0 ⇒ 答应了但没回这个头 ⇒ 查反代是不是把 X-HCM-* 响应头剥了。\n failures.push(\n p.answered > 0\n ? `${META_PATH}#X-HCM-Extension-Source`\n : `${META_PATH}#no-meta-answered`,\n );\n sourceKey = 'unknown';\n }\n\n // 🔴 吃 type 而不只是 modelKey:一个模型从 DATA 变 JSON 时 meta 形状全变,\n // 只哈希 modelKey 的门对此完全无感。\n // 与上面探针的 keys 分开:那份**过滤过空串**(探针配额有限),\n // 这份是**全量**(哈希要如实反映清单,一条缺 modelKey 的记录本身就是漂移信号)。\n const hashLines = models.map((m) => `${m.modelKey ?? ''}|${m.type ?? ''}`).sort();\n const modelHash = sha256(hashLines.join('\\n'));\n\n const rows = await fetchManifestRows(probe, failures);\n\n const versionOut = await probe.get(VERSION_PATH);\n let backendVersion: unknown;\n if (versionOut.error === 'ok' && versionOut.body !== null && versionOut.body !== undefined) {\n backendVersion = versionOut.body;\n } else {\n failures.push(VERSION_PATH);\n backendVersion = { version: 'unavailable' };\n }\n\n return {\n tenantMetaManifestDigest: manifestDigest(rows),\n manifestEntryCount: rows.length,\n extensionSource: sourceKey,\n // 🔴 记下**是从哪个模型探到的**:让「这个值是谁给的」在产物里留痕。\n extensionSourceProbe: p.probeModelKey,\n // 🔴 没有这两个数,`extensionSource: \"unknown\"` 就是一个**不可追问的结论**。\n extensionSourceProbeAttempted: p.attempted,\n extensionSourceProbeAnswered: p.answered,\n modelListHash: modelHash,\n modelCount: models.length,\n backendVersion,\n capabilities: probe.capabilities,\n createdAt: `${now().toISOString().slice(0, 19)}Z`,\n ttlDays,\n coverageNotes: {\n gateComponents: [...GATE_COMPONENTS],\n blindSpots: BLIND_SPOTS,\n // 空数组 = 三个端点都真取到了;非空 = 这份四元组是**残的**,\n // 上面那些 0 和空摘要不代表环境真的空。\n fetchFailures: failures,\n },\n };\n}\n\n/**\n * 比对两份四元组,回**差异清单**(空 = 同源)。\n *\n * 🔴 某个分量没爬到时,它和「爬到了而且相等」在值上同形。两边都没爬到 → 三个分量全等\n * → 门放行一份根本没验过的基线。所以残缺本身也要进差异清单。\n */\nexport function compareFingerprint(\n recorded: Record<string, unknown>,\n current: Record<string, unknown>,\n): string[] {\n const diffs: string[] = GATE_COMPONENTS.filter((c) => recorded[c] !== current[c]);\n\n // capabilities 不进 GATE_COMPONENTS(它不是环境属性,是**爬法**属性),但仍然上报:\n // 两份基线若用不同 capabilities 爬出,那是两种爬法,值得在握手里说一声。\n if (recorded.capabilities !== current.capabilities) diffs.push('capabilities');\n\n for (const [fp, tag] of [\n [recorded, 'recorded'],\n [current, 'current'],\n ] as const) {\n const notes = fp.coverageNotes;\n // 🔴 **`coverageNotes` 整个缺失 ≠ 没有失败**。手改过的、或旧版产出的指纹没有这一节,\n // 此时「有没有取数失败」是**未知**,不是「否」。把未知读成否 ⇒ 放行一份从未验过\n // 残缺与否的基线——「爬过且没失败」与「根本没这个字段」不许同形。\n if (!isPlainObject(notes) || !('fetchFailures' in (notes as object))) {\n diffs.push(`incomplete:${tag}:coverageNotes-missing`);\n continue;\n }\n const raw = (notes as Record<string, unknown>).fetchFailures;\n const list = Array.isArray(raw) ? raw : [];\n // 🔴 报**是什么**残了,不只报**哪一侧**残了。只报侧别会把唯一可行动的信息丢掉:\n // 顾问拿到 `incomplete:recorded` 只知道「有问题」,不知道该查反代(剥了头)\n // 还是查网关分页(#page-cap)。侧别继续留着——只有 recorded 残 ⇒ 旧基线不可信,\n // 重跑 recon;只有 current 残 ⇒ 这次爬取本身有问题,先修环境。\n const gateFailures = [...new Set(list.map(String))]\n .filter((x) => GATE_SOURCES.some((s) => x.startsWith(s)))\n .sort();\n for (const f of gateFailures) diffs.push(`incomplete:${tag}:${f}`);\n }\n return diffs;\n}\n\nfunction sha256(s: string): string {\n return createHash('sha256').update(s, 'utf8').digest('hex');\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nexport type { Outcome, ReconProbe };\n","/**\n * 顾问到现场敲的第一条命令。\n *\n * 回答四个问题,一条锚点一个:**我连上了吗 / 我是谁 / 我有没有 configAdmin / 我站在哪一层**。\n *\n * 🔴 四条锚点**恒存在**。失败时少发一条,顾问看到的是「三条都绿」——他会以为都查过了,\n * 而实际上有一层根本没被体检到。少一条 ≠ 那一层没问题,少一条 = **那一层没被问过**。\n *\n * 移植自 `hcm_nx_core` 的 `tools/fde-kit/hcmnx_kit/doctor.py`(ADR-319 刀 6b)。\n * 行为基线是行为契约清单 §1.4 的 19 条 P。\n */\nimport {\n MANIFEST_PATH,\n MODELS_PATH,\n modelKeys,\n probeExtensionSource,\n} from './fingerprint';\nimport { toPrincipal } from '../auth/principal';\nimport type { ReconProbe } from './probe';\n\nexport type DoctorAnchor = 'connectivity' | 'identity' | 'config-admin' | 'layer';\n\n/** 四条锚点的顺序是固定的:连通 → 我是谁 → 有没有权限 → 站在哪一层。 */\nexport const DOCTOR_ANCHORS: readonly DoctorAnchor[] = [\n 'connectivity',\n 'identity',\n 'config-admin',\n 'layer',\n] as const;\n\nexport interface DoctorCheck {\n anchor: DoctorAnchor;\n ok: boolean;\n detail: string;\n}\n\nexport interface DoctorReport {\n ok: boolean;\n checks: DoctorCheck[];\n}\n\n/**\n * 登录身份。`null` 表示**本工具没有经手登录**——那与「登录了但服务端没给 user」\n * 是两种状态,处置不同,不许合并。\n */\nexport interface ReconIdentity {\n /** token 实际归属的租户(服务端登录响应里的 `user.tenantId`)。 */\n tenantId?: string;\n /** 顾问**要的**租户(命令行给的)。 */\n requestedTenantId?: string;\n username?: string;\n roles?: string[];\n}\n\nexport async function runDoctor(\n probe: ReconProbe,\n identity: ReconIdentity | null,\n): Promise<DoctorReport> {\n const checks: DoctorCheck[] = [];\n\n // ── 锚点 1:连通 ────────────────────────────────────────────────\n const modelsOut = await probe.get(MODELS_PATH);\n const connected = modelsOut.error === 'ok';\n // 🔴 body 声明是 object:服务端完全可能返回 array/string,直接取属性会在客户现场炸——\n // doctor 是第一条命令,它自己不许以裸异常收场。\n const modelsBody = isObj(modelsOut.body) ? modelsOut.body : {};\n checks.push({\n anchor: 'connectivity',\n ok: connected,\n detail: connected\n ? `模型清单可达,共 ${modelsBody.totalCount ?? 0} 个模型`\n : `不可达:${modelsOut.error}(status=${modelsOut.status ?? '?'})`,\n });\n\n // 🔴 从 **meta** 端点探。`/api/system/models` 的响应里**没有**这个头。\n // 读错端点会让 layer 锚点在**每一个**真实环境上报「未回显」,\n // 而单测因为假路由把头挂错了地方而恒绿。\n // 🔴 形状判断 / 空串过滤 / 排序,全在 `modelKeys` 这**唯一一道门**里。\n const keys = modelKeys(modelsOut.body);\n const p = await probeExtensionSource(probe, keys);\n\n // ── 锚点 2:我是谁 ──────────────────────────────────────────────\n // 🔴 `identity` 必须有**自己的**判据。把它的 ok 直接复用 connectivity 的布尔,\n // 「四条锚点」就只有三条真判据,第四条是装饰品。\n // 真判据是:**token 归属的租户 vs 顾问要的租户**。顾问敲错一个租户号,\n // 爬出来的基线**看上去完全正常**,而里面是另一个客户的 schema。\n checks.push({ anchor: 'identity', ...judgeIdentity(identity) });\n\n // ── 锚点 3:有没有 configAdmin ──────────────────────────────────\n const manifestOut = await probe.get(MANIFEST_PATH, { page: 0, pageSize: 1 });\n // 🔴 403 = 没权限(判红);total=0 = 有权限且确实是空的(判绿)。两者不许混——\n // 混判会把一个完全正常的克隆环境(带得走 workspace 绑定、带不走 workspace 文件)\n // 判成权限不足,顾问会去要一个他其实已经有的权限。\n const caOk = manifestOut.error === 'ok';\n const manifestBody = isObj(manifestOut.body) ? manifestOut.body : {};\n const total = caOk ? (manifestBody.total ?? '?') : '?';\n let caDetail: string;\n if (caOk) {\n caDetail =\n `租户 meta 清单可读,共 ${total} 条(0 条是合法状态:` +\n '克隆库带得走 workspace 绑定,带不走 workspace 文件)';\n } else if (manifestOut.status === 401 || manifestOut.status === 403) {\n // 🔴 不可读时**分开两种成因**,它们的下一步完全不同。压进同一句「不可读:{error}」,\n // 顾问只知道「不可读」,不知道该去找客户管理员要角色、还是去找网络组。\n // ⚠️ 压一起时两条 detail 仍然**字面不相等**,所以「两条不相等」这个判据抓不住它——\n // 判据必须落在**下一步指向谁**上。\n caDetail =\n `HTTP ${manifestOut.status}:当前账号**没有 configAdmin 权限**。` +\n '找客户管理员加角色;这不是网络问题';\n } else {\n caDetail =\n `不可读:${manifestOut.error}(status=${manifestOut.status ?? '?'})。` +\n '先排连通/网关,再谈权限。缺 configAdmin 时主锚点算不出来';\n }\n checks.push({ anchor: 'config-admin', ok: caOk, detail: caDetail });\n\n // ── 锚点 4:我站在哪一层 ────────────────────────────────────────\n checks.push({\n anchor: 'layer',\n ok: Boolean(p.sourceKey),\n detail: layerDetail(p, connected),\n });\n\n return { ok: checks.every((c) => c.ok), checks };\n}\n\nfunction judgeIdentity(ident: ReconIdentity | null): { ok: boolean; detail: string } {\n if (ident === null) {\n // 「本工具没有经手登录」≠「登录了但没给 user」。fail-closed:核不了就算没核过。\n return { ok: false, detail: '未经本工具登录,无法核对 token 归属的租户' };\n }\n const want = String(ident.requestedTenantId ?? '').trim();\n const got = String(ident.tenantId ?? '').trim();\n if (!want) {\n // 🔴 命令层忘填 `requestedTenantId` 的形态。落进下面的「不一致」分支会渲染成\n // 「token 归属租户 13,而你要的是 」——一句半截话,且**用一个工具接线缺陷去指控环境**:\n // 顾问会跑去查客户的租户配置,而问题在我们这边。\n return {\n ok: false,\n detail:\n '本工具没有记下「你要的租户」(命令层未填 requestedTenantId),' +\n `无法与 token 归属的租户 ${got || '(未知)'} 核对 —— ` +\n '这是工具接线问题,不是环境问题',\n };\n }\n if (!got) {\n return {\n ok: false,\n detail: `服务端登录响应里没有 user.tenantId,无法核对(你要的是 ${want})`,\n };\n }\n if (want !== got) {\n return {\n ok: false,\n detail:\n `🔴 **token 归属租户 ${got},而你要的是 ${want}** —— ` +\n '你正在爬另一个租户,先停下来。这一对身份会随身份段落进产物索引,' +\n '所以爬完之后在产物里也对得出来;但**落盘路径**上那个租户段取自你给的 ' +\n '`--tenant` —— 目录名照旧是你要的那个号。',\n };\n }\n return {\n ok: true,\n detail:\n `租户 ${got} 核对一致;登录身份 ${ident.username || '(未知)'},` +\n `角色 ${JSON.stringify(ident.roles ?? [])}`,\n };\n}\n\nfunction layerDetail(\n p: { sourceKey: string; probeModelKey: string; attempted: number; answered: number },\n connected: boolean,\n): string {\n if (p.sourceKey) {\n // 🔴 `probeModelKey` 不许算出来又不用:它答的是「在哪个模型上探到的」——\n // 顾问复现时要拿它去打同一个端点。\n return (\n `你站在 \\`${p.sourceKey}\\` 这一层(探自 \\`${p.probeModelKey}\\` 的 meta)。` +\n '不同 sourceKey 的 recon 产物不可互换 —— 两个 FDE 同一条命令会得到两份不同的事实,' +\n '那不是陈旧,是站在不同的层上'\n );\n }\n // 🔴 **几种** unknown 给**不同的下一步**。一句笼统的「无法确定所在层」\n // 会让顾问在一台陌生机器上无从下手。\n if (p.attempted === 0) {\n // 🔴 `attempted === 0` 必须单独说:没有 key 可试时渲染成「试了 0 个模型,一个都没答应」\n // 是把**没试**说成**试了没人应**。而 attempted === 0 本身还有两种成因,说法也不能合:\n // 清单**没取到**(去看 connectivity)vs 清单取到了但**一个可用 modelKey 都没有**\n // (connectivity 是绿的,把人支过去是浪费)。\n return connected\n ? '模型清单取到了,但里面**一个可用 modelKey 都没有**,' +\n '**一个模型都没得试** ⇒ 这不是层的问题:查清单的 modelKey 键名是不是被网关' +\n '改写、或当前账号看不到任何模型'\n : '模型清单没取到,**一个模型都没得试** ⇒ 先看上面 connectivity 那条';\n }\n if (p.answered > 0) {\n return (\n `试了 ${p.attempted} 个模型的 meta,${p.answered} 个答应了,` +\n '但都没回显 X-HCM-Extension-Source ⇒ 先查反代是不是把 X-HCM-* 响应头剥了'\n );\n }\n return (\n `试了 ${p.attempted} 个模型的 meta,**一个都没答应** ⇒ 先查连通与权限,` +\n '再看模型清单是不是空的。⚠️ 本工具只知道「一个都没答应」,' +\n '**分不出**是连通、权限还是中间有人挡了 —— 上一档(答应了但没回显 X-HCM-* 头)' +\n '才指得动反代,这一档指不动任何一方'\n );\n}\n\nfunction isObj(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/** `GET /api/auth/info` —— 服务端认定的 token 归属从这里来。 */\nexport const AUTH_INFO_PATH = '/api/auth/info';\n\n/**\n * 现打一次 `/api/auth/info`,拼出 identity 锚点要的那一对身份。\n *\n * 🔴 **复用 `toPrincipal`,不另写解析**:那个 helper 已经处理了两个来源\n * (`POST /api/auth/login` 的 `user` 与 `GET /api/auth/info` 本体)字段名一致但容器\n * 不同、以及 tenantId 可能是 number 也可能是 UUID 这两件事。再写一份就是第二套口径。\n *\n * 🔴 取不到时回 `null` 而不是空对象:**核不了 ≠ 核过了没问题**。\n * 空对象会落进「服务端没给 user.tenantId」那一档,把一次「问都没问成」\n * 说成「问了,服务端没答」——两者的下一步不同。\n */\nexport async function fetchReconIdentity(\n probe: ReconProbe,\n requestedTenantId: string,\n): Promise<ReconIdentity | null> {\n const out = await probe.get(AUTH_INFO_PATH);\n if (out.error !== 'ok') return null;\n const principal = toPrincipal(out.body);\n if (!principal) return { requestedTenantId };\n return {\n requestedTenantId,\n tenantId: principal.tenantId,\n username: principal.username,\n roles: principal.roles,\n };\n}\n","/**\n * 侦察层对 HTTP 的**窄端口**与**响应分类**。\n *\n * 🔴 为什么是端口而不是搬一份客户端过来(ADR-319 刀 6b 明令):\n * CLI 已经有认证 / endpoint 解析 / token 刷新 / 拦截器这一整条管道\n * (`packages/core/src/client.ts` + `src/http/`)。侦察层再造一份,就是\n * 「集成 reference path 必复用 helper」那条铁律的反面。\n *\n * 但**响应分类**不在那条管道里,而它是侦察层的命根子:顾问打的是一个陌生环境,\n * 「没权限」「没这个端点」「网关吐了 HTML」「连不上」的下一步完全不同。\n * 把它们压成一个布尔,产物里就再也分不出来了。所以这一层必须自己有。\n *\n * ⚠️ `error` 用字符串而不是布尔:要区分的不是「成没成」,而是「没成是哪一种」。\n * 判成功一律写 `error === 'ok'`,别写 `!error`。\n */\nimport type { AxiosInstance } from 'axios';\n\n/** 响应分类的完整取值域。 */\nexport type OutcomeError =\n | 'ok'\n | 'unauthorized'\n | 'forbidden'\n | 'not-found'\n | 'server-error'\n | 'bad-response'\n | 'network';\n\nexport interface Outcome {\n /** 网络层没走通时是 `-1`——它不是 HTTP 状态,别拿去和 4xx 比。 */\n status: number;\n /** 🔴 是 unknown 不是对象:服务端完全可能返回 array / string / number。 */\n body: unknown;\n error: OutcomeError;\n /**\n * 本次响应的头,键一律小写。\n *\n * 🔴 挂在 Outcome 上而不是客户端实例上(Python 侧是 `client.last_headers`):\n * 那种可变字段有「串味」风险——上一次成功的头留到这一次失败时被读到,\n * 而 `probeExtensionSource` 恰恰是紧跟每次 GET 读头。挂在返回值上让串味**结构上不可能**,\n * 契约里那几条「last_headers 必须清空」于是从「要测的行为」变成「构造上成立」。\n */\n headers: Record<string, string>;\n}\n\nexport interface ReconProbe {\n get(path: string, params?: Record<string, unknown>): Promise<Outcome>;\n post?(path: string, body: unknown): Promise<Outcome>;\n getMany?(paths: string[]): Promise<Record<string, Outcome>>;\n postMany?(jobs: Array<{ path: string; body: unknown }>): Promise<Record<string, Outcome>>;\n /** 爬取时声明的 capability 串——它是**爬法**属性,不是环境属性。 */\n readonly capabilities: string;\n}\n\nconst STATUS_CLASS: Record<number, OutcomeError> = {\n 401: 'unauthorized',\n 403: 'forbidden',\n 404: 'not-found',\n};\n\n/**\n * 状态码 → 分类。\n *\n * 🔴 整个 2xx 段都是成功。只认 200 会害得 201/202/206 被归成 `bad-response`——\n * 一个正常的「已创建 / 处理中 / 分段内容」响应被记成失败。\n */\nexport function classifyStatus(status: number): OutcomeError {\n if (status >= 200 && status < 300) return 'ok';\n if (STATUS_CLASS[status]) return STATUS_CLASS[status];\n if (status >= 500) return 'server-error';\n return 'bad-response';\n}\n\n/** 能力头:声明本次爬取用的 capability 串。 */\nexport const CAPABILITIES_HEADER = 'x-hcm-capabilities';\n\n/**\n * 把 `params` urlencode 到 URL 上。\n *\n * 🔴 三个坑各栽过一次:中文必须 urlencode;path 已带 query 时要用 `&` 续接\n * 而不是再来一个 `?`;base 末尾的 `/` 不能和 path 前导的 `/` 拼成 `//`。\n */\nexport function buildUrl(path: string, params?: Record<string, unknown>): string {\n const entries = Object.entries(params ?? {}).filter(([, v]) => v !== undefined && v !== null);\n if (entries.length === 0) return path;\n const qs = entries\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join('&');\n return `${path}${path.includes('?') ? '&' : '?'}${qs}`;\n}\n\nfunction lowerHeaders(raw: unknown): Record<string, string> {\n const out: Record<string, string> = {};\n if (raw && typeof raw === 'object') {\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\n out[k.toLowerCase()] = String(v);\n }\n }\n return out;\n}\n\n/**\n * 把一次 axios 调用的结果归类成 `Outcome`。\n *\n * 🔴 空体的两档不能合:\n * · 2xx 且空体 ⇒ `bad-response`。静默把空当成结果,正是最怕的丢数据形态。\n * 例外是 204 No Content——它**声明**了没有内容,是 `ok` + `body=null`。\n * · 非 2xx 且空体 ⇒ **保留状态分类**,不降级成 `bad-response`。\n * 500 空体仍是 `server-error`;降级会让顾问去查自己请求写错没有,而真相在服务端。\n *\n * 🔴 非 2xx 但**有** body 时 body 要留着:实测反代对大 DSL 查询吐 `504 + HTML 网关超时页`,\n * 顾问拿到 `bad-response` 会去查自己 DSL 键写错没有,而真相是查询超时。\n * 但 HTML 这种非 JSON 体留着也没用且会污染产物 ⇒ 保留分类、body 置空。\n */\nfunction toOutcome(status: number, data: unknown, headers: Record<string, string>): Outcome {\n const cls = classifyStatus(status);\n if (status === 204) return { status, body: null, error: 'ok', headers };\n const empty =\n data === undefined ||\n data === null ||\n (typeof data === 'string' && data.trim() === '');\n if (empty) {\n return { status, body: null, error: cls === 'ok' ? 'bad-response' : cls, headers };\n }\n if (typeof data === 'string') {\n // 字符串体:JSON 解析得出来就当结构化体,解析不出(HTML 错误页等)就置空但保留分类。\n try {\n return { status, body: JSON.parse(data), error: cls, headers };\n } catch {\n return { status, body: null, error: cls === 'ok' ? 'bad-response' : cls, headers };\n }\n }\n return { status, body: data, error: cls, headers };\n}\n\n/** 把既有 axios 管道(已带认证与刷新)适配成侦察层的窄端口。 */\nexport function httpProbe(http: AxiosInstance, capabilities: string): Required<ReconProbe> {\n async function once(\n fn: () => Promise<{ status: number; data: unknown; headers?: unknown }>,\n ): Promise<Outcome> {\n try {\n const res = await fn();\n return toOutcome(res.status, res.data, lowerHeaders(res.headers));\n } catch (e) {\n // 🔴 **本层自己的 bug 不许伪装成 network**:`TypeError` / `ReferenceError` 这类\n // 是编程错误,接住它等于把一条本该看得见的堆栈变成一句「网络不通」,\n // 顾问会去查 VPN,而真相在我们的代码里。单条路径**照抛**。\n // ⚠️ 只有**批量**路径才把它兜成 network(一路炸不许拖垮整批),\n // 这个不对称是刻意的——批量那边的兜底会在产物 note 里写明「去看堆栈」。\n // 🔴 URL 本身非法之类的**本地**错误单独落 bad-response:归进 network\n // 会让顾问白查一遍 VPN。\n const msg = e instanceof Error ? e.message : String(e);\n const local = /Invalid URL|ERR_INVALID_URL|ERR_UNESCAPED_CHARACTERS/i.test(msg);\n // ⚠️ **顺序不能倒过来**:Node 的「Invalid URL」本身就是一个 `TypeError`\n // (`TypeError [ERR_INVALID_URL]`),先判编程错误会把一个**已分类**的畸形 URL\n // 抛成堆栈。实测栽过一次。畸形 URL 是输入问题,不是我们写错了。\n if (!local && isProgrammingError(e)) throw e;\n // TLS / 证书失败同样归 network:它确实「没收到 HTTP 响应」。\n // note 侧负责说清「配 CA 或改 http,查链路是白查」。\n return { status: -1, body: null, error: local ? 'bad-response' : 'network', headers: {} };\n }\n }\n\n const headers = { [CAPABILITIES_HEADER]: capabilities };\n\n const probe: Required<ReconProbe> = {\n capabilities,\n get(path, params) {\n return once(() =>\n http.get(buildUrl(path, params), { headers, validateStatus: () => true }),\n );\n },\n post(path, body) {\n return once(() =>\n http.post(path, body, {\n headers: { ...headers, 'content-type': 'application/json' },\n validateStatus: () => true,\n }),\n );\n },\n /**\n * 🔴 **按原始路径回填、输入全覆盖**:调用方拿 `out[path]` 取结果,\n * 少一个键就会读成 `undefined` 然后当成「没这个东西」。\n * 🔴 一路炸不许拖垮整批——逐路径独立归类。\n */\n async getMany(paths) {\n const pairs = await Promise.all(\n paths.map(async (p): Promise<[string, Outcome]> => [\n p,\n await probe.get(p).catch(() => networkOutcome()),\n ]),\n );\n return Object.fromEntries(pairs);\n },\n async postMany(jobs) {\n const pairs = await Promise.all(\n jobs.map(async (j): Promise<[string, Outcome]> => [\n j.path,\n await probe.post(j.path, j.body).catch(() => networkOutcome()),\n ]),\n );\n return Object.fromEntries(pairs);\n },\n };\n return probe;\n}\n\n/**\n * 是不是**我们自己写错了**(而非传输失败)。\n *\n * axios 的传输错误带 `isAxiosError`,先认它;剩下的 `TypeError` / `ReferenceError` /\n * `SyntaxError` 一律判成编程错误。判据故意写窄:宁可把一个没见过的异常归成 network\n * (它至少落进一个 note 写明「不保证列全、去看堆栈」的桶),\n * 也不要把一个真的传输错误抛成堆栈吓退顾问。\n */\nfunction isProgrammingError(e: unknown): boolean {\n if (!(e instanceof Error)) return false;\n if ((e as { isAxiosError?: boolean }).isAxiosError) return false;\n return e instanceof TypeError || e instanceof ReferenceError || e instanceof SyntaxError;\n}\n\nfunction networkOutcome(): Outcome {\n return { status: -1, body: null, error: 'network', headers: {} };\n}\n\nexport { toOutcome as classifyResponse };\n","/**\n * 一轮 recon 的编排:爬 → 判读 → 建图 → 落盘 → 台账。\n *\n * 🔴 这一层是三份整图产物(topology / valuedomain / mapping)的**唯一**生产者。\n * 各模块单测都能被直接调通,但「接线没接上」完全不表现为报错 —— 只表现为顾问手上\n * 少了三个文件(本仓「能力在但线没接」那条高频形态)。所以这一层的守卫大半在\n * **接线**上,而不在算法上。\n */\nimport { randomBytes } from 'node:crypto';\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport * as path from 'node:path';\n\nimport { compareFingerprint, computeFingerprint, modelEntries, modelKeys } from '../fingerprint';\nimport type { ReconLayout } from '../layout';\nimport type { ReconProbe } from '../probe';\nimport { KIT_CARRY_OVER_KEY } from './carry-over';\nimport { DEFAULT_META_TYPE, crawlMeta, crawlModels, writeCrawlProducts } from './crawl';\nimport { extractFieldRows, writeFieldsIndex } from './fields';\nimport { collectActionGates, writeGates, type Gate, type GateCoverage } from './gates';\nimport { buildMapping, collectMutation, writeMapping } from './mapping';\nimport { renderModelsMd, writeModelsMd } from './modelsmd';\nimport { auditValueDomainPointers } from './pointer-audit';\nimport { refEdges } from './refs';\nimport { buildTopology, writeTopology } from './topology';\nimport {\n COMPLETENESS,\n collectCodeTables,\n collectEnumDomains,\n unsafeTableKeyReason,\n writeValuedomain,\n} from './valuedomain';\n\n/**\n * 🔴 常量本体在 `carry-over.ts`:写这一节的**不止 runner** —— `mapping/_index.json`\n * 与 `valuedomain/_index.json` 由它们各自的模块写,而那两个模块不许 import runner\n * (方向纪律)。这行 re-export 是给命令层与用例用的单一入口。\n */\nexport { KIT_CARRY_OVER_KEY };\n\n/**\n * `--model X` 在模型清单里一个都没匹配上。\n *\n * 🔴 **必须在写盘之前抛**。原先它一路走完编排:`fields.jsonl` 被清成 0 字节、\n * `gates.json` 清空,而摘要是 `rc=0 / modelsQueried:0 / driftSinceLastRecon: []`\n * ——读作「跑成功了,环境没变」。此后 `find` 任何词都印「0 命中 / 索引共 0 字段」,\n * 与「产品没有这个字段」同形(GC3 要分开的两件事),方向是**低报**。\n *\n * 代价在客户现场是最重的一档:FDE 手上**只有这一份基线**,没有源码、没有安装包可以重来,\n * 刚跑完的一次全量(可能几十分钟)会被一个拼错的模型名抹掉,而且没有任何迹象告诉他出事了。\n */\nexport class TargetModelNotFound extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TargetModelNotFound';\n }\n}\n\n// 🔴 这一节回答的是「这份产物里,哪些是这一轮真爬的、哪些是沿用上一轮的」——\n// 少了它,定向重爬产出的基线与全量基线在磁盘上**逐字同形**。\nconst CARRY_NOTE =\n 'mode=targeted 时本轮只重爬了 modelsRecrawled 里的模型;modelsCarriedOver 的字段与只读闸' +\n '是从上一轮落盘的产物里**读回来沿用**的,不是这一轮观测到的。' +\n 'coverage 的其余统计量(modelsQueried / modelsAnswered / unavailable / rejected …)' +\n '只描述**本轮**,不含沿用部分 —— 别拿它们去解释 gates 的总条数。' +\n 'mode=full 时两个列表都为空,这是「全量爬过」而不是「没爬」。' +\n // 🔴 三份新产物在定向重爬下的处置,必须写死在产物里 —— 它们都是**整图/整册**口径的\n // 产物,读者最容易的误读恰恰是「只重爬一个模型 ⇒ 这三份只覆盖那一个模型」。\n ' | topology / valuedomain / mapping 三份产物在 mode=targeted 时**照旧整体重算**,' +\n '但重算用的输入分两半,别混着读:' +\n '① **现打服务端**的那半(码表 rows、mutation-capability)覆盖全部模型,' +\n '**全部是本轮观测**;' +\n '② **吃 meta** 的那半(topology 的边集、mapping 的字段清单、valuedomain/_enums.jsonl)' +\n '对 modelsCarriedOver 里的模型用的是**沿用的 meta**。' +\n 'topologyRecomputed=false 说的正是 ②:这张图这一轮**算过**,只是它的边有一部分来自' +\n '上一轮观测的 meta —— **不是**「没算」。mode=full 时它为 true。';\n\n/**\n * 每个模型的爬取时刻,写进**内容**而不是靠文件 mtime。\n *\n * 🔴 理由是判据本身:**说错年龄比不说更坏**。mtime 会被 `cp`(不带 `-p`)、打包解包、\n * rsync 改写 —— 复评实测 `cp -r` 之后「最早」由 2026-08-13 变 2026-08-16,\n * 沿用行再次被报成刚爬的。而 FDE 的产物被拷来拷去(发同事 / 打包带走 / rsync 上跳板机 /\n * 装进交付包)**是主路径不是边缘场景**,光加一句免责声明等于让他继续读一个错日期。\n *\n * 🔴 落点是**独立文件**,不许往 `meta/<key>.model.json` 里塞键:那些是原样留证的产品\n * 响应体,污染它们就毁掉了「这是服务端原话」这个唯一价值。文件名带 `_kit` 前缀 ——\n * 一眼看得出是工具写的,产品从不发它。\n */\nexport const KIT_CRAWLED_AT_FILE = '_kitCrawledAt.json';\n\nconst CRAWLED_AT_NOTE =\n 'models[<模型>] = 该模型的 meta **最后一次真正被爬下来**的时刻(UTC)。' +\n '定向重爬只更新本轮重爬的那些,沿用的保持原值不动 —— 这份文件正是「这份基线有多老」' +\n '的唯一可靠依据:文件 mtime 会被 cp/打包/rsync 改写,它不会。' +\n '值为 null = 这个模型是沿用来的、而产出它的那一轮**没有记录时间戳**' +\n '(基线由更早版本的 kit 产出)—— 与「刚爬的」是两件事,不许合并。';\n\n/**\n * 「带轮次戳的产物」名单是**判据的单一来源**:`runRecon` 按它写、`inspectRound` 按它读。\n *\n * 🔴 两边各写一份清单,下一个新产物必然只补一边 —— 而漏补的表现是\n * 「那份产物永远被判成本轮的」,一个只在中断时才暴露的静默缺陷。\n */\nexport const ROUND_STAMPED_PRODUCTS = [\n 'topology.json',\n 'valuedomain/_index.json',\n 'mapping/_index.json',\n] as const;\n\n/**\n * 🔴 三档**不许合并**,它们的下一步动作完全不同:\n * complete = 所有产物同属一轮,且这一轮跑到了最后(台账落了盘);\n * mixed = 存在不同轮次的产物 / 有产物没戳 / 有产物缺席 / 这一轮没跑完 —— 都要指名道姓;\n * unknown = 产物里根本没有 roundId(更早版本的 kit 爬的),**判不了**。\n * ⚠️ `unknown` 这一档不许省:老基线不带戳,把它判成 complete 或 mixed 都是撒谎。\n */\nexport const ROUND_STATES = ['complete', 'mixed', 'unknown'] as const;\nexport type RoundState = (typeof ROUND_STATES)[number];\n\n/**\n * 档位 → 退出码。**这张表是唯一一处做这个映射的地方**,命令层只查表、一个字都不重判。\n *\n * 🔴 `unknown` 也非零:它是「判不了」,而脚本把「判不了」当成「没问题」继续往下走,\n * 正是这一整节要防的事。\n */\nexport const ROUND_EXIT_CODES: Record<RoundState, number> = { complete: 0, mixed: 1, unknown: 2 };\n\n/**\n * grep 的头**只写一处**:`ROUND_NOTE`(产物里的说明)与摘要的 `recheckWith`\n * (跑完那一刻的提示)抄两份必然漂移,而漂移的表现是「产物教的读法与摘要教的不一样」。\n *\n * ⚠️ 用 `-e … -e …` 而不是 `\\|` 交替:后者是 GNU BRE 扩展,BSD grep(客户可能是 macOS /\n * 精简版 busybox)上未必认 —— 一条**在部分机器上敲不通**的命令与敲不通没有区别。\n * 两个模式缺一不可:`roundId` 判「是不是同一轮」,`productsWrittenThisRound` 判\n * 「这一轮跑完了没有」,只 grep 前者会把「对齐」读成「没问题」。\n */\nconst GREP_RECIPE_HEAD = `grep -H -e '\"roundId\"' -e '\"productsWrittenThisRound\"'`;\n\n/** 零网络子命令的名字。产物里教的读法与摘要里给的提示都从这里取,不抄第二份。 */\nexport const ROUND_SUBCOMMAND = 'hcm delivery recon round';\n\nconst ROUND_NOTE =\n 'roundId = 这一轮 recon 的身份(起跑时刻 + 随机后缀,可排序、同秒两轮也不会撞)。' +\n 'productsWrittenThisRound = 本轮**真正落盘**的产物相对路径 —— 判据是文件真的写出来了,' +\n '不是「本轮打算写」;`null` 表示这一轮没跑到写这份台账那一步(中断 / 断网 / 进程被杀),' +\n '与 `[]`(跑到了、一份都没写)是两件事。' +\n 'topology.json / valuedomain/_index.json / mapping/_index.json 各自带一份同名的 roundId:' +\n '它们与这里对不上,就说明那几份产物是**别的轮次**留下的 —— ' +\n '此时 `mapping/` 里「没有某个模型」可能只是本轮没写到那里,不是产品的结论。' +\n ' | 怎么当场判(零网络):' +\n `① 肉眼对账,**在产物目录里**执行 \\`${GREP_RECIPE_HEAD} ${KIT_CRAWLED_AT_FILE} ` +\n `${ROUND_STAMPED_PRODUCTS.join(' ')}\\`。` +\n // 🔴 三种读法缺一不可:漏掉中间那一档时,顾问会把「四行对齐」直接读成「没问题」——\n // 而真机第 14.4s 中断产出的正是「四行全对齐、轮次却没跑完」。\n '读法有三种:**四个 roundId 对不齐** = 混装(那几份产物是别的轮次留下的);' +\n '**四个都对齐、但 productsWrittenThisRound 是 `null`** = 这一轮**没跑完**' +\n '(写完最后一份产物之前就断了,真机实测存在这一档,别把「对齐」读成「没问题」);' +\n '**一行都 grep 不到** = 这份基线是更早版本的 kit 爬的,判不了。' +\n // 🔴 ② 教的命令必须**今天真能敲**:指一条敲不通的命令比不指更坏。\n // 参数是**产物目录本身**(就是你现在站的这个目录),不是产物根 ——\n // 少一层「在同一租户下选哪一份基线」的推断,就少一处会错的地方。\n '② 让工具说人话(complete / mixed / unknown 三档 + 指名道姓说出哪份产物旧到哪一轮):' +\n `在产物目录里执行 \\`${ROUND_SUBCOMMAND} --dir . --output table\\`;` +\n '退出码 0=complete / 1=mixed / 2=unknown,写进脚本当前置闸也可以。' +\n '⚠️ 它要 `@hcmai/cli` 装过(`npx @hcmai/cli` 亦可);装不了就走 ①,' +\n 'grep 是任何机器上都有的那条路。' +\n '判据本体是 `@hcmai/sdk` 的 `inspectRound()` / `renderRound()`,只读文件、零网络,' +\n '产物被单独拷走后照样直接调得动。';\n\n/**\n * 同一句话必须**在 topology.json 自己身上**再说一遍:产物是会被**单独拷走**的文件\n * (发同事 / 打包带走 / rsync 上跳板机),口径只写进 gates.json 的 `_kitCarryOver` 与\n * CLI 摘要,拿走 topology.json 的人就会把整张图当成这一轮观测到的。\n */\nconst TOPOLOGY_RECOMPUTE_NOTE =\n 'recomputedThisRound=true:本轮是全量爬取,图里每一条边都算自**这一轮**取回的 meta。' +\n 'recomputedThisRound=false:本轮是定向重爬(--model),这张图**照样整体重算了** —— ' +\n '节点取自本轮的模型名册,边集也整体重算 —— 但 modelsRecrawledThisRound 之外的模型,' +\n '它们的边来自**上一轮落盘的 meta**。所以 false 的意思是「算过,部分输入是旧的」,' +\n '**不是**「这一轮没算」。沿用了哪些模型、上一轮是什么时候爬的:' +\n // 🔴 指路必须**指到那一层**:这一节挂在 gates.json 的 `coverage` 下,不在顶层。\n // 少写一层,顾问在顶层 grep 不到就会以为这份基线根本没记沿用情况 ——\n // 与「指一个不存在的文件」同一类错。\n `见 gates.json 的 coverage.${KIT_CARRY_OVER_KEY} 与产物根的 ${KIT_CRAWLED_AT_FILE};` +\n `mapping/_index.json 与 valuedomain/_index.json 也各带一份同名的 ${KIT_CARRY_OVER_KEY}(顶层)。`;\n\n// ── 轮次身份 ──────────────────────────────────────────────────────────────────\n// 🔴 **为什么要有它**(2026-08-16 真机复现,不是推演):一轮 recon 跑到一半被 Ctrl-C,\n// 产物目录里就是**两代混装**的,而且没有任何标记。实测(localhost:8087 / 租户 13,\n// 先跑完一轮再起一轮并在第 8.0s 发 SIGINT)按 mtime 分成两代:\n// 本轮重写:models.json / meta×742 / unavailable.json / gates.json / index×2 /\n// _kitCrawledAt.json / topology.json\n// 沿用上一轮:valuedomain×74 / mapping×743 / fingerprint.json\n// 于是顾问打开 `mapping/` 看不到某个模型 —— 这与「那个模型的 modelKey 不安全、被拒写\n// 并留了痕」**完全同形**,而后者是产品的正常输出。GC3 的老形态:「本轮没写」与\n// 「本轮写了、结论是没有」被塞进了同一个观测。\n//\n// 🔴 roundId 的形态:**起跑时刻(可排序)+ 随机后缀(可区分)**,两半缺一不可。\n// · 只有时刻 ⇒ 同一秒起跑的两轮拿到同一个 id ⇒ 混装被判成「同一轮」;\n// · 只有随机 ⇒ 顾问拿到两个 id 说不出哪个更早,而 `mixed` 的文案要求\n// **指名道姓说出旧到哪一轮** —— 说不出先后就等于没说。\n// ⚠️ 随机那半用 `crypto.randomBytes`(不是 `Math.random`):这里要的恰恰是\n// 「不可能与另一轮撞上」,而 `Math.random` 在同一进程里是可预测的伪随机序列。\n\nfunction two(n: number): string {\n return String(n).padStart(2, '0');\n}\n\n/** `20260816T075429Z` —— roundId 的前半,给顺序。 */\nfunction roundStamp(d: Date): string {\n return (\n `${d.getUTCFullYear()}${two(d.getUTCMonth() + 1)}${two(d.getUTCDate())}` +\n `T${two(d.getUTCHours())}${two(d.getUTCMinutes())}${two(d.getUTCSeconds())}Z`\n );\n}\n\n/** `2026-08-16T07:54:29Z` —— 台账里的人读时刻。 */\nfunction isoStamp(d: Date): string {\n return d.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/** roundId 前半 → Date;形状不对就回 null(**宁可少说一句,也不许说错一个时间**)。 */\nfunction parseRoundStamp(stamp: string): Date | null {\n const m = /^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z$/.exec(stamp);\n if (!m) return null;\n const [, y, mo, d, h, mi, s] = m as unknown as string[];\n return new Date(Date.UTC(+y!, +mo! - 1, +d!, +h!, +mi!, +s!));\n}\n\n/** `20260816T075429Z-a1b2c3d4`。前半给顺序,后半给身份(理由见上方注释)。 */\nexport function newRoundId(started: Date): string {\n return `${roundStamp(started)}-${randomBytes(4).toString('hex')}`;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\n\n/**\n * 读一份 JSON 产物,**只**把「文件不在」与「不是合法 JSON」兜成 `undefined`。\n *\n * 🔴 别的异常照旧往上抛:越界闸(`LayoutContainmentError`)、权限、IO 是系统性故障,\n * 吞掉它们会让一份处处是窟窿的基线看起来正常。口径与 `valuedomain` / `mapping`\n * 的落盘 catch 一致。\n */\nfunction readJsonSoft(file: string): unknown {\n let text: string;\n try {\n text = readFileSync(file, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return undefined;\n throw err;\n }\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\nfunction readCrawledAt(layout: ReconLayout): Record<string, unknown> {\n const body = readJsonSoft(path.join(layout.dir, KIT_CRAWLED_AT_FILE));\n const models = isRecord(body) ? body.models : null;\n return isRecord(models) ? models : {};\n}\n\n/**\n * 新爬的记当前时刻,沿用的原值不动(拿不到原值就显式记 `null`)。\n *\n * 🔴 每个模型的时刻用**本轮起跑时刻**(`started`),不是「写这一行的那一刻」:\n * 同一轮里逐个取 now 会让 742 行分布在十几秒里,于是「这一行是哪一轮爬的」只能靠猜。\n * 用起跑时刻,它与 `roundStartedAt` 逐字相等,两者可互相印证。\n * 误差上界就是一轮的时长(本机实测 15.16s),远小于这份数据的用途(判基线多老)。\n */\nfunction writeCrawledAt(\n layout: ReconLayout,\n freshKeys: string[],\n carriedKeys: string[],\n roundId: string,\n started: Date,\n): Record<string, string | null> {\n const previous = readCrawledAt(layout);\n const now = isoStamp(started);\n const models: Record<string, string | null> = {};\n for (const key of carriedKeys) {\n // 🔴 `previous[key]` 可能是 undefined/null —— 那是「沿用了但不知道它多老」,\n // 与「刚爬的」是两个状态(GC3),落成显式 null 而不是悄悄补上 now。\n const prev = previous[key];\n models[key] = typeof prev === 'string' ? prev : null;\n }\n for (const key of freshKeys) models[key] = now;\n const sorted: Record<string, string | null> = {};\n for (const key of Object.keys(models).sort()) sorted[key] = models[key] ?? null;\n layout.writeJson(KIT_CRAWLED_AT_FILE, {\n note: CRAWLED_AT_NOTE,\n roundNote: ROUND_NOTE,\n roundId,\n roundStartedAt: now,\n // 🔴 这一刻**必然**是 null:本轮还没写完,台账要等到最后一份产物落盘才知道。\n // 恒发键(GC3):`null` = 没跑到那一步,`[]`/清单 = 跑到了。\n // ⚠️ 正因为每一轮起手都会把它重置成 null,它才能当「这一轮跑完了没有」的见证 ——\n // 上一轮留下的清单不会冒充本轮的。\n productsWrittenThisRound: null,\n models: sorted,\n });\n return sorted;\n}\n\n/**\n * 一轮的**最后一笔**:把本轮真正落盘的产物清单补进 `_kitCrawledAt.json`。\n *\n * 🔴 为什么要**重写一次**这个文件而不是新开一份:`writeCrawledAt` 必须早写\n * (它要在 `carryOver` 读完旧值之后、meta 落盘那一段之前定下轮次身份),\n * 而台账只有跑到最后才知道内容。两次写同一份文件的代价是一次小 IO,\n * 换来的是「这份文件本身就是本轮跑完的见证」——中断在最后一份产物之后、\n * 台账之前时,三份索引的 roundId 全都对得上,此时唯一还能说话的就是它。\n * (真机第 14.4s 那次中断正是这个形态:只有 fingerprint.json 是上一轮的。)\n *\n * 🔴 判据是**文件真的写出来了**,不是「本轮打算写」——口径与 `codeTableFiles` 那段一致。\n * 承重的是**推导**那一半:`products` 里的 meta 文件先按 `rejectedMetas` 剔过\n *(把它换成 `Object.keys(metas)` 会红),`vdWritten` / `mappingPaths` 也都是写入方\n * 自己回报的「真写出去的那些」。\n *\n * ⚠️ 下面这道 `existsSync` 是**第二条独立证据链**,而它在当前写入方组合下\n * **一次都走不到** —— 这是**实测**的,不是推的:把过滤换成「排掉任何一个就抛」跑全套用例,\n * 探针零命中。⇒ 把它删掉**不会有任何用例红**(变异实测确认)。\n * 留着的理由不是「它抓到过什么」,而是:上面那条推导**自己也可能错,而错法恰好是不报错的**\n * (产物里凭空多一行台账,与真写出去了同形)—— 这与 `danglingValueDomainPointers` 那对\n * 双链是同一条纪律。代价是每份产物一次 stat(真机 1567 条约 5–7ms,整轮的 0.04%)。\n * 🔴 别为它编一条「测得出差别」的用例:写入方不变就造不出那个差别,硬造出来的只会是\n * 一条测夹具的用例。\n */\nfunction finishRound(\n layout: ReconLayout,\n models: Record<string, string | null>,\n roundId: string,\n started: Date,\n products: string[],\n): string[] {\n const written = [...new Set(products.filter((rel) => rel && existsSync(path.join(layout.dir, rel))))].sort();\n layout.writeJson(KIT_CRAWLED_AT_FILE, {\n note: CRAWLED_AT_NOTE,\n roundNote: ROUND_NOTE,\n roundId,\n roundStartedAt: isoStamp(started),\n productsWrittenThisRound: written,\n models,\n });\n return written;\n}\n\n/** 产物绝对路径 → 相对产物根的 posix 路径。写盘用的 rel 就长这样,两端才对得上。 */\nfunction relOf(layout: ReconLayout, p: string): string {\n const rel = path.relative(layout.dir, p);\n // `..` 开头说明它压根不在产物根下 —— 那是编排配置错,原样交出去比拼一个假 rel 强。\n if (!rel || rel.startsWith('..')) return p;\n return rel.split(path.sep).join('/');\n}\n\n/**\n * → `[读出来的状态, roundId]`。状态取自**产物本身**,不做任何推断。\n *\n * 🔴 「没有戳」必须是它自己的一档,不能与「戳着别的轮次」合并 ——\n * 判据「A 与 B 的 roundId 不同」的前提是**两边都有 roundId**,前提不成立时\n * `null !== \"…\"` 会碰巧得出正确结论而理由是错的,反过来两边都没戳时\n * `null === null` 会得出「同一轮 ⇒ complete」,那就是撒谎。\n */\nfunction productRoundId(root: string, rel: string): [string, string | null] {\n const p = path.join(root, rel);\n if (!existsSync(p)) return ['missing', null];\n const body = readJsonSoft(p);\n if (body === undefined || !isRecord(body)) return ['unreadable', null];\n const rid = body.roundId;\n if (typeof rid !== 'string' || !rid) return ['unstamped', null];\n return ['stamped', rid];\n}\n\nexport interface RoundProduct {\n path: string;\n state: 'this-round' | 'older-round' | 'unstamped' | 'missing' | 'unreadable';\n roundId: string | null;\n}\n\nexport interface LedgerFilesMissing {\n checked: number;\n count: number;\n files: string[];\n}\n\nexport interface RoundResult {\n state: RoundState;\n roundId: string | null;\n roundStartedAt: unknown;\n finished: boolean | null;\n productsWrittenThisRound: number | null;\n ledgerFilesMissing: LedgerFilesMissing | null;\n products: RoundProduct[];\n dir: string;\n note: string;\n}\n\n/**\n * 读一份**已经落盘**的基线,回答「它是不是一轮完整跑出来的」。\n *\n * 只读文件、零网络:顾问在气隙机器上、或者刚被 Ctrl-C 打断之后,手上就只有这几个文件\n * (被打断的那一轮**不会**打印任何摘要 —— 所以这个判断不能只活在摘要里)。\n */\nexport function inspectRound(root: string): RoundResult {\n const raw = readJsonSoft(path.join(root, KIT_CRAWLED_AT_FILE));\n const ledger: Record<string, unknown> = isRecord(raw) ? raw : {};\n const rid = ledger.roundId;\n const roundId = typeof rid === 'string' && rid ? rid : null;\n const rawWritten = ledger.productsWrittenThisRound;\n const written = Array.isArray(rawWritten) ? rawWritten : null;\n\n // 🔴 台账点了名的产物,**现在还在不在盘上**。\n // 与中断那一档同形的另一张脸:中断留下混装基线而工具主动盖 `complete` 的章;\n // 这里是**拷贝 / 裁剪**留下残缺基线而工具同样盖章 —— 而后者概率高得多:\n // 前者要撞 0.14s 的窗口,本档只要顾问 scp / U 盘 / 压缩包漏拷一次,或为脱敏删几份。\n // 「FDE 的产物被拷来拷去是主路径」是本 kit 在时间戳那一轮就立下的判断。\n //\n // 🔴 **全查,不抽查**(先量了才定的):真机 1567 条台账全 stat 三次分别\n // 6.68 / 5.54 / 5.13ms —— 整轮 recon 14.4s 的 0.04%。抽查会引入「抽了多少、怎么抽的」\n // 这一串要额外交代的口子(no silent caps),而全查根本不需要那些交代。\n // `checked` 恒发:查了几份必须说得出来。\n // 🔴 `null` = **查不了**(台账是 null:这一轮没跑到写台账那一步,或老基线没有这一节),\n // 与 `{count: 0}`(查过了、一份不缺)是两个答案,不许同形(GC3)。\n let ledgerMissing: LedgerFilesMissing | null = null;\n if (written !== null) {\n const gone = written\n .filter((rel): rel is string => typeof rel === 'string' && !existsSync(path.join(root, rel)))\n .sort();\n ledgerMissing = { checked: written.length, count: gone.length, files: gone };\n }\n\n const products: RoundProduct[] = [];\n for (const rel of ROUND_STAMPED_PRODUCTS) {\n const [readState, pid] = productRoundId(root, rel);\n const state =\n readState === 'stamped'\n ? roundId && pid === roundId\n ? 'this-round'\n : 'older-round'\n : (readState as RoundProduct['state']);\n products.push({ path: rel, state, roundId: pid });\n }\n\n let state: RoundState;\n let finished: boolean | null;\n if (roundId === null && products.every((p) => p.roundId === null)) {\n // 一个戳都没有 ⇒ 判不了。**不许**因为「三份都没有戳所以都一样」而报 complete。\n state = 'unknown';\n finished = null;\n } else {\n finished = written !== null;\n // 🔴 第三个合取项:台账点名的文件缺了一份,这份基线就不是「完整一轮」——\n // 它可能是拷贝时漏带、也可能被人删过,但无论哪种,拿它下结论都会踩到\n // 「这里没有某个模型」=「产品里真的没有」那个同形陷阱。\n state =\n roundId && finished && !ledgerMissing?.count && products.every((p) => p.state === 'this-round')\n ? 'complete'\n : 'mixed';\n }\n\n return {\n state,\n roundId,\n roundStartedAt: ledger.roundStartedAt ?? null,\n finished,\n productsWrittenThisRound: written !== null ? written.length : null,\n ledgerFilesMissing: ledgerMissing,\n products,\n dir: root,\n note: ROUND_NOTE,\n };\n}\n\n/**\n * 「台账点了名、盘上却没有」是**第三种事**,措辞不许与下面那四档任何一条重合(连子串都不许):\n * 带戳索引的 `missing` = 那份产物**根本没有**(这一轮没写到那里),下一步是重跑 recon;\n * 本档 = **本轮写过、现在却找不到了**,下一步是先问「这份基线是怎么拷过来的」。\n * 把两者说成同一句话,顾问就会去重跑一个其实拷漏了的基线,而漏的那几份下次还会漏。\n */\nexport const LEDGER_MISSING_WORD = '本轮写过、现在却**找不到了**(拷贝 / 裁剪时漏带,或事后被删)';\n\nexport const PRODUCT_STATE_WORDS: Record<Exclude<RoundProduct['state'], 'this-round'>, string> = {\n 'older-round': '是**别的轮次**留下的',\n unstamped: '在盘上,但**没有轮次戳**(更早版本的 kit 写的)',\n missing: '**不在盘上**',\n unreadable: '在盘上但**读不出 JSON**',\n};\n\n/**\n * 两个 roundId 的时间差 —— 「旧到哪一轮」要说得出**多旧**才有用。\n * 解析不出来就不说(宁可少说一句,也不许说错一个时间)。\n */\nexport function roundAge(roundId: unknown, other: unknown): string {\n if (typeof roundId !== 'string' || typeof other !== 'string') return '';\n const a = parseRoundStamp(roundId.split('-')[0] ?? '');\n const b = parseRoundStamp(other.split('-')[0] ?? '');\n if (!a || !b) return '';\n const seconds = Math.trunc((a.getTime() - b.getTime()) / 1000);\n // 起跑时刻不早于本轮 —— 说不出「早多久」就别说。(同秒起跑的两轮靠随机后缀区分,\n // 它们的 roundId 不同但时刻相同,此时只报 id 不报时差。)\n if (seconds <= 0) return '';\n // 🔴 秒级差别不许渲染成 `0h00m`:那读起来像「同一时刻」,而它恰恰是\n // 「两轮挨得很近」——中断后立刻重跑正是这个形态(真机实测两轮相隔 25s)。\n if (seconds < 60) return `,比本轮早 ${seconds}s`;\n return `,比本轮早 ${Math.floor(seconds / 3600)}h${two(Math.floor((seconds % 3600) / 60))}m`;\n}\n\n/** 把 `inspectRound` 的结论说成人话。三档措辞**互不重叠**,读者不必回头查文档。 */\nexport function renderRound(result: RoundResult): string {\n const lines: string[] = [`基线:${result.dir}`];\n if (result.state === 'unknown') {\n lines.push(\n '状态:unknown —— 这份基线里**一个 roundId 都没有**(更早版本的 kit 爬的)。',\n '⇒ 判不了它是不是一轮完整产物。**别把它当 complete,也别当 mixed** ——' +\n '重跑一次 recon 才会带上轮次身份。',\n );\n return `${lines.join('\\n')}\\n`;\n }\n\n lines.push(\n `本轮 roundId:${result.roundId ?? '(无)'}(起跑 ${\n (result.roundStartedAt as string | null) ?? '未知'\n })`,\n );\n if (result.state === 'complete') {\n lines.push(\n `状态:complete —— ${result.products.length} 份带戳的产物同属这一轮,` +\n `本轮共落盘 ${result.productsWrittenThisRound} 个文件。`,\n '⚠️ complete 说的是「**同一轮**」,不是「全量爬过」:定向重爬(`--model X`)' +\n '同样会把三份产物整体重写,照样是 complete。全量与否看 gates.json 的 ' +\n `\\`coverage.${KIT_CARRY_OVER_KEY}\\`。`,\n );\n return `${lines.join('\\n')}\\n`;\n }\n\n lines.push('状态:mixed —— 这份基线**不是一轮完整跑出来的**:');\n for (const p of result.products) {\n if (p.state === 'this-round') continue;\n let detail = PRODUCT_STATE_WORDS[p.state];\n if (p.state === 'older-round') {\n detail += `(roundId ${p.roundId}${roundAge(result.roundId, p.roundId)})`;\n }\n lines.push(` · ${p.path} ${detail}`);\n }\n if (result.finished === false) {\n lines.push(\n ' · 本轮的 productsWrittenThisRound 没有落盘 ⇒ 这一轮**没跑完**' +\n '(Ctrl-C / 断网 / 进程被杀都会这样)',\n );\n }\n const gone = result.ledgerFilesMissing;\n if (gone?.count) {\n // 🔴 点名到文件,且**给全量条数**:只说「有文件缺失」等于让顾问自己去比 1567 行。\n // 名字最多列 5 个(再多在终端里是刷屏),但条数与「查了几份」永远给全 ——\n // 完整名单在结构化结果的 `ledgerFilesMissing.files` 里,不靠这行渲染保真。\n const shown = gone.files.slice(0, 5).join('、');\n const more = gone.count > 5 ? '(仅列前 5 个,完整名单见 ledgerFilesMissing.files)' : '';\n lines.push(\n ` · 台账点名的 ${gone.checked} 份产物里,有 **${gone.count}** 份${LEDGER_MISSING_WORD}:${shown}${more}`,\n );\n }\n lines.push(\n '⇒ 🔴 别拿这些产物里「没有某个模型」当结论 —— 那可能只是**本轮没写到那里**,' +\n '与「产品里真的没有」同形。重跑一次完整 recon。',\n );\n return `${lines.join('\\n')}\\n`;\n}\n\nexport interface CarryOver {\n mode: 'full' | 'targeted';\n onlyModel: string | null;\n modelsRecrawled: string[];\n modelsCarriedOver: string[];\n gatesCarriedOver: number;\n problems: string[];\n topologyRecomputed: boolean;\n note: string;\n}\n\n/**\n * 定向重爬时,把上一轮产物里**其它模型**的部分读回来。\n *\n * 🔴 不做这一步,`--model X` 就是拿单个模型的产物**整体覆写**全量基线:\n * 实测「全量 recon(2 模型)→ `--model Alpha`」后 `index/fields.jsonl` 由 2 行变 1 行、\n * `gates.json` 只剩 1 个模型,而 `models.json` 仍写着 2、`meta/Bravo.model.json` 还躺在那儿\n * —— 产物自相矛盾,且没有任何迹象。\n *\n * 🔴 问题**只记不抛**:读不回旧产物时,定向重爬应当退化成「只有这个模型」的基线,\n * 而不是整条命令失败 —— 但退化这件事必须写进产物,否则它与「上一轮本来就只有这个模型」同形。\n */\nfunction collectCarryOver(\n layout: ReconLayout,\n freshMetas: Record<string, Record<string, unknown>>,\n): {\n carriedMetas: Record<string, Record<string, unknown>>;\n carriedGates: Gate[];\n problems: string[];\n} {\n const problems: string[] = [];\n const carriedMetas: Record<string, Record<string, unknown>> = {};\n const metaDir = path.join(layout.dir, 'meta');\n const suffix = `.${DEFAULT_META_TYPE}.json`;\n let names: string[] = [];\n try {\n names = readdirSync(metaDir).filter((n) => n.endsWith(suffix)).sort();\n } catch (err) {\n // 目录不存在是正常的(首次就用 --model 跑)。别的 IO 错照旧抛。\n if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err;\n }\n for (const name of names) {\n const key = name.slice(0, -suffix.length);\n if (key in freshMetas) continue; // 本轮重爬过,用新的\n const file = path.join(metaDir, name);\n let body: unknown;\n try {\n body = JSON.parse(readFileSync(file, 'utf8'));\n } catch (e) {\n problems.push(`${name}: 读不回上一轮的 meta(${(e as Error).message}),该模型这次不进索引`);\n continue;\n }\n if (isRecord(body)) carriedMetas[key] = body;\n else {\n problems.push(\n `${name}: 上一轮的 meta 载荷是 ${Array.isArray(body) ? 'list' : body === null ? 'null' : typeof body}` +\n ' 不是对象,该模型这次不进索引',\n );\n }\n }\n\n const carriedGates: Gate[] = [];\n const oldGates = readJsonSoft(path.join(layout.dir, 'gates.json'));\n if (oldGates === undefined) {\n // 没有上一轮 gates.json 是正常的(首次就用 --model 跑)。记一句,不当故障。\n problems.push('gates.json: 没有可沿用的上一轮闸表(不存在或读不出 JSON)');\n }\n const oldList = isRecord(oldGates) && Array.isArray(oldGates.gates) ? oldGates.gates : [];\n for (const g of oldList) {\n if (isRecord(g) && !(String(g.model) in freshMetas)) carriedGates.push(g as unknown as Gate);\n }\n\n return { carriedMetas, carriedGates, problems };\n}\n\n/** 定向重爬时给 `models.md` 加一条抬头。产物是会被**单独拷走**的文件。 */\nfunction carryOverBanner(carryOver: CarryOver): string {\n if (carryOver.mode !== 'targeted') return '';\n return (\n `> ⚠️ **本轮是定向重爬(\\`--model ${carryOver.onlyModel}\\`),不是全量。**\\n` +\n `> 这一轮真正爬的只有:${carryOver.modelsRecrawled.join(', ') || '(无)'};\\n` +\n `> 另有 **${carryOver.modelsCarriedOver.length}** 个模型的字段与 ` +\n `**${carryOver.gatesCarriedOver}** 条只读闸是**沿用上一轮产物**,不是这一轮观测到的。\\n` +\n carryOver.problems.map((p) => `> - ⚠️ ${p}\\n`).join('') +\n '\\n'\n );\n}\n\nexport interface RunReconOptions {\n state?: string;\n onlyModel?: string | null;\n /** 服务端认定的 token 归属。落进 `valuedomain/_index.json`,`null` = 调用方没告诉我。 */\n identity?: unknown;\n}\n\nexport interface ReconSummary {\n driftSinceLastRecon: string[] | null;\n modelsQueried: number;\n metasCrawled: number;\n unavailable: number;\n gates: number;\n fieldRows: number;\n fieldsDropped: number;\n carryOver: CarryOver;\n crawledAtUnknown: number;\n topology: Record<string, unknown>;\n valuedomain: Record<string, unknown>;\n mapping: Record<string, unknown>;\n round: Record<string, unknown>;\n coverage: GateCoverage & { [KIT_CARRY_OVER_KEY]: CarryOver };\n dir: string;\n}\n\nexport async function runRecon(\n probe: ReconProbe,\n layout: ReconLayout,\n opts: RunReconOptions = {},\n): Promise<ReconSummary> {\n const state = opts.state ?? '';\n const onlyModel = opts.onlyModel ?? null;\n\n // 🔴 轮次身份在**打第一发请求之前**定下:它是这一轮的身份,不是产物的属性。\n // 放到写盘时再取,中断在写盘之前的那些形态就没有身份可言了。\n const started = new Date();\n const roundId = newRoundId(started);\n\n const modelsBody = await crawlModels(probe);\n // 🔴 走同一道门 `modelKeys()`:`models[]` 里混进一个非 dict 时,就地 `.get(...)`\n // 会在**已经爬完、正要落最后一份产物**的时候炸掉,前面所有网络工作全部白费。\n // ⚠️ 顺带把顺序从「服务端返回序」变成「排序」:探针与闸表本来就按 key 排序,\n // 这里跟着排能让两次爬取的**请求顺序**也确定,出问题时日志可比对。\n let keys = modelKeys(modelsBody);\n if (onlyModel) {\n keys = keys.filter((k) => k === onlyModel);\n if (keys.length === 0) {\n // 🔴 **写盘之前**就拒绝。「打错了一个模型名」与「这个模型确实什么都没有」\n // 必须分得开(GC3):前者一个字节都不该落盘,后者才走正常编排产出空索引。\n const total = Array.isArray(modelsBody.models) ? modelsBody.models.length : 0;\n throw new TargetModelNotFound(\n `--model '${onlyModel}' 在模型清单里一个都没匹配上(清单共 ${total} 个模型)。` +\n '已拒绝写盘,现有基线原样保留。请核对模型名大小写与拼写,' +\n '或先跑一次不带 --model 的全量 recon。',\n );\n }\n }\n\n const { metas: freshMetas, unavailable } = await crawlMeta(probe, keys);\n const gateResult = await collectActionGates(probe, keys, state);\n\n const carried = onlyModel\n ? collectCarryOver(layout, freshMetas)\n : { carriedMetas: {} as Record<string, Record<string, unknown>>, carriedGates: [] as Gate[], problems: [] as string[] };\n\n // ⚠️ 两边**互斥**(`collectCarryOver` 里 `if (key in freshMetas) continue`),所以这里的\n // 合并顺序**观测不到** —— 实测:把顺序反过来跑全套用例**一条都不红**,另有探针跑全套\n // 确认两个键集零交集。真正承重的是那条互斥本身,它由 `carryOver.modelsCarriedOver`\n // 钉住(用例断言它恰是 `['Bravo']`;把那句 continue 删掉会红)。\n // 顺序仍写成「fresh 在后」,是为了让读者一眼看出万一互斥被破坏时**谁该赢**。\n const metas: Record<string, Record<string, unknown>> = { ...carried.carriedMetas, ...freshMetas };\n const gates: Gate[] = [...carried.carriedGates, ...gateResult.gates];\n\n // 三个收集器形状一致 —— 都返回 (产物, 被丢了什么)。\n const { rows, dropped } = extractFieldRows(metas);\n\n const carryOver: CarryOver = {\n mode: onlyModel ? 'targeted' : 'full',\n onlyModel,\n modelsRecrawled: Object.keys(freshMetas).sort(),\n modelsCarriedOver: Object.keys(carried.carriedMetas).sort(),\n gatesCarriedOver: carried.carriedGates.length,\n problems: carried.problems,\n // 🔴 整图产物的诚实标注。语义见 CARRY_NOTE 与 topology.json 的 recomputedNote:\n // false ≠「没算」,而是「算过、边集里有一部分输入是沿用的 meta」。\n topologyRecomputed: !onlyModel,\n note: CARRY_NOTE,\n };\n const coverage: GateCoverage & { [KIT_CARRY_OVER_KEY]: CarryOver } = {\n ...gateResult.coverage,\n [KIT_CARRY_OVER_KEY]: carryOver,\n };\n\n // 🔴 只写**这一轮真爬的** meta,不回写沿用的那些。两个理由:\n // ① 没爬就别声称爬过 —— 回写会把沿用文件的 mtime 刷成「刚刚」;mtime 是产物的诚实\n // 属性,不该由一次没发生的爬取去改写;\n // ② 沿用文件本来就是上一轮写的,内容逐字相同,回写没有任何收益。\n //\n // 🔴 **台账排在第一份产物之前**(评审真机复现)。原先它排在 `writeCrawlProducts` /\n // `writeGates` / `writeFieldsIndex` **之后**,于是有一个真实窗口:中断落在这中间时,\n // `models.json` / `meta/*` / `gates.json` / `index/*` 已经是**新一代**(这四样都不带\n // roundId、也不在 ROUND_STAMPED_PRODUCTS 里),而根部台账与三份索引仍全是**上一轮**的、\n // 彼此自洽 ⇒ `inspectRound` 判 **complete**。那正是本刀要治的病本身,只不过工具还给它\n // 盖了个章 —— **主动判错比沉默不判更坏**。真机量到的窗口 ≈ 0.14s / 14.6s ≈ 1%\n // (models.json +0.000s → index/fields.jsonl +0.128s,而 valuedomain +6.9s /\n // mapping +7.9s / fingerprint +8.7s),且它会随模型数与磁盘变慢而变长。\n //\n // 🔴 原先排在后面的唯一约束是「要先读上一版来沿用时间戳」—— 而**读与写可以拆开**:\n // `writeCrawledAt` 内部先读再落盘,这一刻盘上还是上一轮的 `_kitCrawledAt.json`,读得到;\n // `collectCarryOver` 读的是 `meta/*` 与 `gates.json`,也都还没被覆盖(它排在更前面)。\n // ⇒ 提前它不破坏任何前提。\n const crawledAt = writeCrawledAt(\n layout,\n Object.keys(freshMetas).sort(),\n Object.keys(carried.carriedMetas).sort(),\n roundId,\n started,\n );\n const crawlWritten = writeCrawlProducts(layout, modelsBody, freshMetas, unavailable);\n const gatesPath = writeGates(layout, gates, coverage);\n const fieldsPath = writeFieldsIndex(layout, rows);\n const mdPath = writeModelsMd(\n layout,\n carryOverBanner(carryOver) + renderModelsMd(modelsBody, metas, gates, dropped),\n );\n\n // 🔴 本轮台账的第一段。meta 那一批**不能**按「盘上有没有这个文件」判 ——\n // 沿用上一轮的 meta 文件同样在盘上(定向重爬时它们恒在),拿 exists 判会把\n // 「上一轮写的」算成「本轮写的」,正是这份台账要区分的那件事。\n // 权威判据来自写入方自己:`writeCrawlProducts` 把落盘被拒的 modelKey **就地追加**\n // 进了 `unavailable`(error=unsafe-model-key),减掉它们剩下的就是真写出去的那些。\n const rejectedMetas = new Set(\n unavailable.filter((e) => e?.error === 'unsafe-model-key').map((e) => e.modelKey),\n );\n const products: string[] = [\n crawlWritten.models!,\n crawlWritten.unavailable!,\n KIT_CRAWLED_AT_FILE,\n relOf(layout, gatesPath),\n relOf(layout, fieldsPath),\n relOf(layout, mdPath),\n ];\n for (const k of Object.keys(freshMetas).sort()) {\n if (!rejectedMetas.has(k)) products.push(`meta/${k}.${DEFAULT_META_TYPE}.json`);\n }\n\n // ── topology / valuedomain / mapping ──────────────────────────────────────\n // 🔴 落在这里而不是紧跟 extractFieldRows:下面这一段**要打网络**(码表分页 +\n // 每模型的 mutation-capability),而上面那几份产物早就算完了。先把算完的落盘,\n // 一旦下面这段在客户 VPN 上炸掉,顾问手上至少还有 models/meta/gates/fields 四份,\n // 而不是整轮白跑。编排的**依赖顺序**没变,换的只是「已算完的先落盘」。\n\n // 🔴 名册 ⊇ metas 的键:节点集取名册,`refEdges` 才能把「目标不在名册」这一档\n // 分流进 dangling。定向重爬时名册也是**本轮**取回的,旧的只有 metas 那一半。\n const known = new Set([...Object.keys(metas), ...modelKeys(modelsBody)]);\n const { edges, dangling } = refEdges(metas, known);\n\n // 🔴 `modelsWithMeta` 传的是 `metas`(**本轮进了边集计算的那些**),不是 `freshMetas`:\n // 定向重爬时沿用的 meta 同样贡献了边,它们的出边是**可知**的(只是来自上一轮观测,\n // 那件事由 `_kitCarryOver` / `recomputedThisRound` 另行披露,两个维度不许混)。\n // 名册里而不在这批里的节点,出边一条都算不出来,却与「爬过且真的无依赖」同形地\n // 躺在 waves[0] 里 —— 传了它,产物才点得出名字(8087 实测 761 − 742 = 19 个)。\n const topo = buildTopology(edges, dangling, [...known].sort(), Object.keys(metas).sort());\n topo.recomputedThisRound = !onlyModel;\n topo.modelsRecrawledThisRound = Object.keys(freshMetas).sort();\n topo.recomputedNote = TOPOLOGY_RECOMPUTE_NOTE;\n // 🔴 这份产物会被**单独拷走**,所以轮次身份要长在它自己身上,而不是只写在根部台账里。\n topo.roundId = roundId;\n products.push(relOf(layout, writeTopology(layout, topo)));\n\n const entries = modelEntries(modelsBody);\n const { tables: codeTables, problems: vdProblems } = await collectCodeTables(probe, entries);\n const enumRows = collectEnumDomains(metas);\n const { written: vdWritten, writeProblems: vdWriteProblems } = writeValuedomain(\n layout,\n codeTables,\n enumRows,\n {\n identity: opts.identity,\n // `modelEntriesSeen` 不许省:名册加载失败给出空 list 时,产物会与「名册里一条\n // DYNAMIC 都没有」逐字节同形(GC3)。null 是「调用方没告诉我」,这里我们知道。\n modelEntriesSeen: entries.length,\n carryOver,\n roundId,\n // 取数期台账也要进产物:与落盘期的 writeProblems 分两个键,\n // 两侧现在都是「摘要给条数 + 产物给明细」。\n problems: vdProblems,\n },\n );\n\n // 🔴 喂给 `buildMapping` 的是「对照表**真的写出来了**的那些」,不是 `collectCodeTables`\n // 的键集。两者的差额(modelKey 含 `/` 或以 `.` 开头被本地判据拒掉、layout 越界闸拦下)\n // **全部是悬空指针** —— 而 mapping 自己的 WORDING 写着「指一个不存在的文件比不指更糟」。\n // ⚠️ 判据是**文件在不在**,不是「这张码表爬成功了没有」:爬失败的表照样落一份\n // `completeness=unavailable` 的文件,指向它是**有效**的。\n // 🔴 **两个条件缺一不可**:光判「路径在不在 `written` 里」会被 kit 自己的产物文件骗过\n // —— `written` 里**恒有** `valuedomain/_index.json`(索引自己写的那一份),于是一张\n // `modelKey=\"_index\"` 的 DYNAMIC 码表**明明被拒了**,却因为那个路径存在而被判成\n // 「文件写出来了」,mapping 照着发出指针,两条对账链**都报干净**,而顾问点开拿到的是\n // 索引不是对照表。⇒ 先问 `valuedomain` **自己那道判据**(同一个函数,不是抄一份),\n // 再看路径。\n const writtenPaths = new Set(vdWritten);\n const codeTableFiles = new Set(\n Object.keys(codeTables).filter(\n (k) =>\n unsafeTableKeyReason(k) === null &&\n writtenPaths.has(path.join(layout.dir, 'valuedomain', `${k}.json`)),\n ),\n );\n\n const { mutation, problems: mutProblems } = await collectMutation(probe, Object.keys(metas).sort());\n const sheets = buildMapping(metas, edges, mutation, codeTableFiles);\n // 🔴 第二条**独立**证据链:上面那个推导(用 writeValuedomain 的返回值反推)自己也可能\n // 错,而错法恰好是不报错的。这里不吃那个推导,直接按 sheet 里印出来的指针去看盘。\n // 两条链互不依赖,差集才有意义 —— 只有一条时,「推导错了」与「产物没问题」同形。\n const danglingPointers = auditValueDomainPointers(layout, sheets);\n // 🔴 三份台账**同时**进产物与摘要:摘要跑完就没了,而产物是被单独拷走的那一半。\n products.push(...vdWritten.map((p) => relOf(layout, p)));\n const mappingPaths = writeMapping(layout, sheets, {\n problems: mutProblems,\n danglingValueDomain: danglingPointers,\n carryOver,\n roundId,\n });\n products.push(...mappingPaths.map((p) => relOf(layout, p)));\n // `writeMapping` 的契约:`_index.json` **在最后**,其余每条对应一份写成的 sheet。\n // 写不下来的 modelKey 不抛也不静默 —— 名字在 `mapping/_index.json` 的 `unsafeModelKeys`\n // 里,这里只报差额,好让「ls mapping/ 与 modelCount 对不上」在摘要里就看得见。\n const sheetsWritten = Math.max(mappingPaths.length - 1, 0);\n\n // 🔴 **先读旧指纹再覆盖**,把「上次爬取以来环境变没变」算出来放进 summary。\n // 这不是漂移门:**本处只记录、不阻断**。阻断那一半住在 `apply` 的漂移门里。\n let previous: unknown = readJsonSoft(path.join(layout.dir, 'fingerprint.json'));\n // 🔴 首次爬取(没有旧指纹)与「比对过、没变化」是**两个不同的结论**,\n // 不能都表示成空 list(GC3)。前者用 `null`。\n if (!isRecord(previous)) previous = null;\n\n const current = await computeFingerprint(probe);\n products.push(relOf(layout, layout.writeJson('fingerprint.json', current)));\n const drift =\n previous === null\n ? null\n : compareFingerprint(previous as Record<string, unknown>, current as unknown as Record<string, unknown>);\n\n // 🔴 **一轮的最后一笔**:写完台账才算跑完。放在这里不是随意 —— 它同时是\n // 「这一轮跑到了终点」的唯一见证(见 `finishRound` 的说明)。\n const productsWritten = finishRound(layout, crawledAt, roundId, started, products);\n // 摘要里也给一份判断:跑完那一刻就能看见「我刚写出来的这份基线是不是完整一轮」。\n //\n // 🔴 **摘要不是唯一落点,也不该是**:被中断的那一轮压根不会打印摘要,而那恰恰是\n // 要诊断的那一轮。所以同一个判断有三条路,且都不依赖这次进程还活着:\n // ① 产物自证:四份文件各带 roundId,`grep` 就能对账(命令写在 `_kitCrawledAt.json`\n // 的 roundNote 里 —— 产物被单独拷走时它跟着走);\n // ② `inspectRound()` / `renderRound()`:零网络、只读文件,给三档结论 + 人话;\n // ③ 本摘要字段:跑完那一刻的复核。\n const roundResult = inspectRound(layout.dir);\n\n const byCompleteness: Record<string, number> = {};\n for (const c of COMPLETENESS) {\n byCompleteness[c] = Object.values(codeTables).filter((v) => v.completeness === c).length;\n }\n\n return {\n driftSinceLastRecon: drift,\n modelsQueried: keys.length,\n metasCrawled: Object.keys(freshMetas).length,\n unavailable: unavailable.length,\n gates: gates.length,\n fieldRows: rows.length,\n // 🔴 被丢掉的字段必须有落点。摘要给条数,models.md 给明细 ——\n // 「记下来了但没人看得见」等于没记。\n fieldsDropped: dropped.length,\n // 🔴 索引里有多少行不是这一轮爬的,必须能一眼看出来 —— 否则定向重爬产出的\n // 基线与全量基线在摘要里同形。\n carryOver,\n // 🔴 时间戳未知的模型数必须能一眼看到:它是「沿用了但不知道多老」的落点,\n // 与「刚爬的」不许合并(GC3)。\n crawledAtUnknown: Object.values(crawledAt).filter((v) => v === null).length,\n // 🔴 三段统计一律**带上自己的 problems / 差额**,不许只报好数字(no silent caps):\n // 明细在各自的产物文件里,摘要给条数 + 指向文件,两处缺一份都等于没记\n // (只进摘要 ⇒ 产物被单独拷走后就没了;只进产物 ⇒ 跑完那一刻看不见)。\n topology: {\n nodes: topo.nodeCount,\n edges: topo.edgeCount,\n cycles: (topo.cycles as unknown[]).length,\n selfLoops: (topo.selfLoops as unknown[]).length,\n unordered: (topo.unordered as unknown[]).length,\n blockedByCycles: (topo.blockedByCycles as unknown[]).length,\n // 三个「没进依赖图」的口子都要报数:edgeCount 与图里用掉的边数的差额\n // 就是它们仨,不报的话没人说得清差在哪。\n dangling: (topo.dangling as unknown[]).length,\n // 🔴 「出边不可知」的节点数(接线后恒是数字,不再是 null)。明细在 topology.json\n // 的 nodesWithoutMeta.models 里,摘要给条数 —— 两处缺一份都等于没记。\n nodesWithoutMeta: (topo.nodesWithoutMeta as { count: number }).count,\n excludedEdges: (topo.excludedEdges as { count: number }).count,\n edgesOutsideNodes: (topo.edgesOutsideNodes as { count: number }).count,\n recomputedThisRound: topo.recomputedThisRound,\n file: 'topology.json',\n },\n valuedomain: {\n codeTableCount: Object.keys(codeTables).length,\n enumFieldCount: enumRows.length,\n // 四档全列(含产不出来的那一档),缺键会被读成「这个 kit 还不懂这一档」。\n byCompleteness,\n // 🔴 两份台账**不许合并**:problems 是取数时的(爬不到 / 没取满 / 分页可疑),\n // writeProblems 是落盘时的(modelKey 当不了文件名)。前者「有这张表但内容残」,\n // 后者「压根没有这份文件」—— 下一步动作完全不同。\n problems: vdProblems,\n writeProblems: vdWriteProblems,\n file: 'valuedomain/_index.json',\n },\n mapping: {\n modelCount: sheets.length,\n sheetsWritten,\n // 差额恒有值:`ls mapping/ | wc -l` 与 modelCount 对不上时,这里先给出数,\n // 名字在 mapping/_index.json 的 unsafeModelKeys 里。\n sheetsNotWritten: sheets.length - sheetsWritten,\n problems: mutProblems,\n // 🔴 悬空的 valueDomain 指针。空列表 = **对过账且一条都没有**,\n // 不是「没对过」(GC3)—— 这份对账是 codeTableFiles 那条推导的独立复核。\n danglingValueDomainPointers: danglingPointers,\n file: 'mapping/_index.json',\n },\n // 🔴 轮次身份 + 本轮台账的条数。产物与摘要两处落点:明细在 `_kitCrawledAt.json` 里,\n // 摘要给结论与条数。\n round: {\n roundId,\n state: roundResult.state,\n productsWritten: productsWritten.length,\n file: KIT_CRAWLED_AT_FILE,\n // 中断过的基线不会走到这里,所以必须告诉顾问**事后**怎么问同一个问题。\n // ⚠️ 这里写的是**今天真能敲**的那条,且**两个模式都要 grep**:只看 roundId\n // 会把「四行对齐」读成「没问题」,而真机实测存在「对齐但没跑完」那一档。\n // 🔴 命令头与 roundNote 同源(`GREP_RECIPE_HEAD`),不抄第二份。\n recheckWith: `cd ${layout.dir} && ${GREP_RECIPE_HEAD} ${KIT_CRAWLED_AT_FILE} ${ROUND_STAMPED_PRODUCTS.join(' ')}`,\n // 子命令形态(零网络)—— 它给的是 complete/mixed/unknown 三档人话 + 退出码,\n // 比 grep 多一层判读。🔴 命令名与 roundNote 的 ② 同源(`ROUND_SUBCOMMAND`),\n // 不抄第二份:抄两份的表现是「产物教的命令与摘要教的不一样」。\n recheckCommand: `${ROUND_SUBCOMMAND} --dir ${layout.dir} --output table`,\n },\n coverage,\n dir: layout.dir,\n };\n}\n","/**\n * 定向重爬的披露标记。\n *\n * 🔴 `_kit` 前缀:这一节是**工具加的**,产品从不发它。下游遍历产物顶层键的人\n * 必须一眼看得出哪些是服务端说的、哪些是我们说的。\n *\n * 🔴 **为什么单独成一个模块**:有**三份**产物要写这一节 —— `gates.json`(编排层写)、\n * `mapping/_index.json`、`valuedomain/_index.json`(后两份由各自模块写)。\n * 而 `mapping` / `valuedomain` 不许反过来 import 编排层(那是它们的上层,import 会成环)。\n * 剩下的选择只有两个:把字面量抄三份,或者收到一个三方都在其下的地方。\n * 抄三份必然漂移,而漂移的表现是**顾问 grep `_kitCarryOver` 只搜到两份产物** ——\n * 第三份看着就像「这一轮没有沿用」,正是这一节存在的意义所要防的那种误读。\n */\nexport const KIT_CARRY_OVER_KEY = '_kitCarryOver';\n","/**\n * 不可用条目的 note 表(契约清单 §3.1 的 `recon.note` 那一族)。\n *\n * 🔴 note 不是装饰:顾问在客户现场拿到的就是 `unavailable.json`,\n * 分类串(`not-found` / `network` / `bad-response`…)本身给不出下一步。\n * 每一类的措辞必须**互斥**,且把顾问导向**不同**的排查方向——\n * 两类都写「网络层失败」,等于把两条完全不同的路合成一条死路。\n */\n\n/**\n * 产品对 `@HiddenFromRegistry` 模型恒返 404。\n * 2026-08-16 实测(8087 / 租户 13):761 个模型里 19 个如此(18 个 *Index +\n * 1 个 WorkspaceCheckpointRevisionModel)。**这是分类结果,不是故障。**\n */\nconst NOTE_404 =\n 'HTTP 404:多为 @HiddenFromRegistry 型模型(如各种 *Index),' +\n '产品对其恒返 DATA_NOT_FOUND。属正常分类结果,非故障';\n\n/**\n * 🔴 `network` **不等于「网络坏了」**。四类根本不是网络故障的成因也落这里,\n * 而「网络层失败」这种文案会把顾问直接支去查 VPN。\n *\n * 🔴 **「已实测四类」不等于「一共就四类」,这个区别是承重的**:批量路径的兜底是\n * 「接住一切异常」,**按构造就是开放的**。所以 note **不许**写成\n * 「排除这几条就是网络问题」那种带排除法的完整枚举——那是把一句不准但**没声称完整**的话,\n * 升级成一句**声称完整**的断言,而那个前提并不成立,反而更容易被照着做错。\n */\nconst NOTE_NETWORK =\n '未收到 HTTP 响应(恒 status=-1)。🔴 本桶**不只装网络故障**;下面是**已实测**四类,' +\n '**不保证列全**(批量路径兜底接住一切异常,本层任何新异常都会落这里):' +\n '① modelKey 含空格/控制字符 → URL 畸形,工具侧问题;' +\n '② 端点漏写 http:// 且带端口(如 localhost:8087)→「unknown url type」,配置问题;' +\n '③ https + 自签/内网 CA → TLS 证书问题,配 CA 或改 http,查链路是白查;' +\n '④ 工具自身异常 → 批量路径兜成本桶,**去看 stderr 里的堆栈**。' +\n '都不像才轮到查 VPN / DNS / 链路。' +\n '占比也是判据:整批全落本桶 → 端点/证书/网络;只个别几条 → 那个 modelKey 或工具 bug';\n\n/**\n * 🔴 分类串的**字面意思会骗人**,note 必须先把这件事说破:\n * 401/403/404 与 5xx 单独分档,**其余非 2xx(400/409/422…)一律落这里**,\n * 正文是不是完好的 JSON 根本不参与判定——写成「收到响应但不是可解析的 JSON」\n * 会让顾问去查一个不存在的解析问题。\n *\n * 🔴 note 里那个错误信封的形状**回源自线上**(2026-08-16 打三个端点实测):\n * 顶层键集恒为 `['error','success']`,`error` 键集恒为 `['code','details','message']`。\n * ⚠️ 别按 Java 源码「改正」这句:`success` 由更外层包装补上,源码里 grep 不到它——\n * **回源到源码 ≠ 回源到线上**。反过来 `error.field` 在 Java DTO 里**有**,\n * 但三次实测一次都没出现(null 被省略),所以它只能以「源码有、未观测」的措辞出现。\n */\nconst NOTE_BAD_RESPONSE =\n '分类按 status 走,**不代表正文一定不是 JSON**。按 status 分流:' +\n 'status=-1 → 请求**根本没发出去**(同样**不保证列全**):' +\n 'modelKey 含**非 ASCII**(如中文)→ 发送阶段编码失败;' +\n '或端点漏写 http:// 且**不带端口**(如 myserver)→「unknown url type」。' +\n '⚠️ 含**空格**的 modelKey、以及漏 scheme 但**带端口**的端点,落的是 network 桶不在这里;' +\n 'status=2xx → 服务端返回了空体、非 JSON 正文或 JSON null(「没给到数据」,非「空集合」)' +\n '—— 只有这一档的**分类原因**是解析失败;' +\n '其余 status(400/409/422 等未被单独分档的非 2xx)→ 诊断在**状态码**里,' +\n '正文很可能是一份**完好**的业务错误信封' +\n '({\"error\":{\"code\",\"message\",\"details\"},\"success\":false},人话在 details 里;' +\n 'error 还可能带 field,该键回源自 Java DTO、2026-08-16 实测三端点均未出现),' +\n '值得看一眼;但本工具的 GET 路径不保留非 2xx 的正文(产物里没有它),' +\n '要看这段人话得照原样重打一次该请求(curl 即可)。也可能是网关/反代吐的 HTML 兜底页';\n\nexport const UNAVAILABLE_NOTES: Record<string, string> = {\n 'not-found': NOTE_404,\n forbidden: 'HTTP 403:当前账号对该模型无权限。产物覆盖面因此受限,须记进权限画像',\n unauthorized: 'HTTP 401:token 失效或未认证',\n network: NOTE_NETWORK,\n 'bad-response': NOTE_BAD_RESPONSE,\n 'server-error': '服务端 5xx',\n};\n\n/**\n * `UNAVAILABLE_NOTES` 的兜底。\n *\n * 🔴 只写「未分类失败」等于把分类串本身也丢了——那是顾问唯一能拿去搜的线索 ⇒ 说清下一步。\n * 🔴 **本函数不下「新分类串只可能来自某处」这种封闭断言**:那条封闭性由\n * 「每个失败类都有 note,集合恰好相等」那条守卫断成 `==`,不在这里用散文声称。\n */\nexport function unclassifiedNote(error: string): string {\n return (\n `未分类失败:分类串 ${JSON.stringify(error)} 不在工具的 note 表里。` +\n '本层里给这个参数喂值的是 `crawlMeta` 与 `collectActionGates` 两处,' +\n '两处传的都是同一进程里探针给的分类串(同一份安装)——' +\n '在这条链路上它与 note 表本该逐一对上。别的来源本模块**没有查过**;' +\n '把上面这个分类串原样报回来'\n );\n}\n\n/** 取 note:认识就给分类 note,不认识就给可搜索的兜底。 */\nexport function noteFor(error: string): string {\n return UNAVAILABLE_NOTES[error] ?? unclassifiedNote(error);\n}\n","/**\n * 侦察层第一跳:模型清单与元数据爬取(契约清单 §3.1,32 条)。\n *\n * 🔴 本模块最贵的不是「怎么发请求」,是两条记账纪律:\n * ① **每个模型都必须到达一个终态**(`metas.size + unavailable.length === 请求数`);\n * ② **`declared` vs `observed` 双向对账**。\n * 少一个模型不会报错,只会让基线悄悄变小——这正是「静默漏登」的形态。\n * 而 ① 对「某个模型被悄悄漏掉」是**盲的**(漏掉的既不在分子也不在分母),\n * 所以必须有 ② 那条互不依赖的第二证据链。\n */\nimport { MODELS_PATH } from '../fingerprint';\nimport type { Outcome, ReconProbe } from '../probe';\nimport type { ReconLayout } from '../layout';\nimport { LayoutContainmentError } from '../layout';\nimport { noteFor } from './notes';\n\n/**\n * 🔴 `_kit` 前缀:这个键是**工具加的**,产品从不发它。\n * 下游遍历 `models.json` 顶层键的人必须一眼看得出哪些是服务端说的、哪些是我们说的。\n */\nexport const KIT_PREMISE_KEY = '_kitPremiseCheck';\n\n/**\n * 🔴 默认形态是**实测打过**的裸路径 `/api/models/{k}/meta`\n * (2026-08-16 打满 761 个 key:742×200 + 19×404,5.0s)。\n * `type=` 只在调用方显式要别的视图(list/info/view)时才追加——\n * 给默认路径缀一个从没实测过的 `?type=model`,等于把「实测成立的请求」换成「推出来的请求」。\n */\nexport const DEFAULT_META_TYPE = 'model';\n\nexport function metaPath(modelKey: string, metaType: string = DEFAULT_META_TYPE): string {\n // 🔴 模型名**不做 URL 转义**:转义会让 `meta/<modelKey>.json` 不再能按 modelKey grep 回去,\n // 而畸形 URL 本身已被探针归成 status=-1,note 里有分流判据。\n const base = `/api/models/${modelKey}/meta`;\n return metaType === DEFAULT_META_TYPE ? base : `${base}?type=${metaType}`;\n}\n\nexport interface UnavailableEntry {\n modelKey: string;\n metaType: string;\n status: number;\n error: string;\n note: string;\n /** 只有 `unexpected-shape` 那一支带它。 */\n bodyPreview?: string;\n}\n\nexport interface PremiseCheck {\n totalCountMatchesListLength: boolean;\n responseShape: string;\n status: number;\n error: string;\n collidedWithServerKey: boolean;\n serverValue?: unknown;\n}\n\nfunction modelRows(body: unknown): Array<Record<string, unknown>> {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return [];\n const rows = (body as Record<string, unknown>).models;\n if (!Array.isArray(rows)) return [];\n return rows.filter((m): m is Record<string, unknown> => Boolean(m) && typeof m === 'object' && !Array.isArray(m));\n}\n\n/** 响应体的形状名,用于 `responseShape`:区分 object / array / string / null…… */\nfunction shapeOf(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'list';\n return typeof value === 'object' ? 'dict' : typeof value;\n}\n\n/**\n * 爬模型清单,并**就地**注入一份前提自证。\n *\n * 🔴 服务端返 list/str 时不能直接往上挂键——那在客户现场就是一条裸堆栈。\n * 回落成空清单,但**不许**让空清单伪装成健康:`totalCount(0) === len([])` 恰好成立,\n * 一份垃圾响应就此看起来像「爬到了,就是空的」。\n */\nexport async function crawlModels(probe: ReconProbe): Promise<Record<string, unknown>> {\n const out = await probe.get(MODELS_PATH);\n const healthy = Boolean(out.body) && typeof out.body === 'object' && !Array.isArray(out.body);\n const body: Record<string, unknown> = healthy\n ? { ...(out.body as Record<string, unknown>) }\n : { totalCount: 0, typeCounts: {}, models: [] };\n const rows = Array.isArray(body.models) ? (body.models as unknown[]) : undefined;\n const premise: PremiseCheck = {\n // 前提显式断言:totalCount 与 models 长度不符时,后续任何「共 N 个模型」都不成立。\n totalCountMatchesListLength: Boolean(healthy && rows !== undefined && body.totalCount === rows.length),\n responseShape: shapeOf(out.body),\n status: out.status,\n error: out.error,\n // 撞键与否**恒有值**:没有这个键时「没撞」与「没检查过」在产物里同形。\n collidedWithServerKey: Object.prototype.hasOwnProperty.call(body, KIT_PREMISE_KEY),\n };\n if (premise.collidedWithServerKey) {\n // 🔴 就地注入不许**静默覆盖**服务端的同名键:服务端说了什么,\n // 是这份基线里最不能被工具吃掉的东西。撞键时原值原样留在 serverValue 里。\n premise.serverValue = body[KIT_PREMISE_KEY];\n }\n body[KIT_PREMISE_KEY] = premise;\n return body;\n}\n\nexport interface CrawlMetaResult {\n metas: Record<string, Record<string, unknown>>;\n unavailable: UnavailableEntry[];\n}\n\n/**\n * 逐模型爬 meta。**每个 key 都会到达一个终态**:要么进 `metas`,要么进 `unavailable`。\n */\nexport async function crawlMeta(\n probe: ReconProbe,\n modelKeys: string[],\n metaType: string = DEFAULT_META_TYPE,\n): Promise<CrawlMetaResult> {\n const paths = new Map<string, string>();\n for (const key of modelKeys) paths.set(metaPath(key, metaType), key);\n const results = probe.getMany\n ? await probe.getMany([...paths.keys()])\n : await sequentialGet(probe, [...paths.keys()]);\n\n const metas: Record<string, Record<string, unknown>> = {};\n const unavailable: UnavailableEntry[] = [];\n for (const [path, key] of paths) {\n const out = results[path];\n if (out === undefined) {\n // 并发丢任务:必须是一条**显式记录**,否则表现为「这个模型好像没有 meta」。\n unavailable.push({\n modelKey: key,\n metaType,\n status: -1,\n error: 'not-crawled',\n note: '并发批次里没有返回该 path',\n });\n continue;\n }\n const isObject = Boolean(out.body) && typeof out.body === 'object' && !Array.isArray(out.body);\n if (out.error === 'ok' && isObject) {\n metas[key] = out.body as Record<string, unknown>;\n continue;\n }\n if (out.error === 'ok') {\n // 🔴 HTTP 成功但载荷形状不对。这一支必须与「HTTP 失败」**分开**记:\n // 混进下面那支会得到一条 `error: \"ok\"` 的 unavailable 条目,读起来自相矛盾,\n // 且把两种完全不同的成因糊成一团——顾问看了不知道该找网关还是找我们。\n unavailable.push({\n modelKey: key,\n metaType,\n status: out.status,\n error: 'unexpected-shape',\n note:\n `HTTP ${out.status} 成功但载荷是 ${shapeOf(out.body)},` +\n '不是 meta 对象(载荷形状问题,不是取数失败)',\n bodyPreview: JSON.stringify(out.body).slice(0, 200),\n });\n continue;\n }\n // 🔴 **刻意不加 `bodyPreview`**:这一支拿不到正文,加一个叫 bodyPreview 却装不下 body\n // 的字段比不加更糟——它会让读产物的人以为「看过正文了,没线索」。\n // 理由要说准:本函数走的全是 GET,非 2xx 那档探针**压根没保留正文**。\n unavailable.push({ modelKey: key, metaType, status: out.status, error: out.error, note: noteFor(out.error) });\n }\n return { metas, unavailable };\n}\n\n/** 探针没有 `getMany` 时的退化路径:逐条打,键仍按原始 path 回填。 */\nexport async function sequentialGet(probe: ReconProbe, paths: string[]): Promise<Record<string, Outcome>> {\n const out: Record<string, Outcome> = {};\n for (const p of paths) out[p] = await probe.get(p);\n return out;\n}\n\nexport interface Reconciliation {\n declaredUnavailable: string[];\n observedUnavailable: string[];\n declaredNotObserved: string[];\n observedNotDeclared: string[];\n agrees: boolean;\n note: string;\n}\n\n/**\n * 两条**互不依赖**的证据链对拍:清单自己声明的 `error` vs 我们实际打出来的失败。\n *\n * 🔴 差集**有方向**,两个方向指向完全相反的结论:\n * · `declaredNotObserved`(清单说坏、我们没记)⇒ **漏爬**;\n * · `observedNotDeclared`(清单说好、我们爬坏了)⇒ 权限 / 超时 / 网关,**不是**隐藏模型。\n */\nexport function reconcileUnavailable(\n modelsBody: unknown,\n unavailable: UnavailableEntry[],\n): Reconciliation {\n const declared = [\n ...new Set(modelRows(modelsBody).filter((m) => m.error).map((m) => String(m.modelKey))),\n ].sort();\n const observed = [...new Set(unavailable.map((e) => e.modelKey).filter(Boolean))].sort();\n const dset = new Set(declared);\n const oset = new Set(observed);\n return {\n declaredUnavailable: declared,\n observedUnavailable: observed,\n declaredNotObserved: declared.filter((k) => !oset.has(k)),\n observedNotDeclared: observed.filter((k) => !dset.has(k)),\n agrees: declared.length === observed.length && declared.every((k) => oset.has(k)),\n note:\n 'declaredNotObserved = 模型清单声明不可用、但本轮没有对应失败记录 ⇒ 漏爬;' +\n 'observedNotDeclared = 模型清单说可用、本轮却失败 ⇒ 查权限/超时/网关,' +\n '不是「发现了隐藏模型」',\n };\n}\n\n/**\n * 落盘。\n *\n * ⚠️ 本函数**会向 `unavailable` 追加**(越界 modelKey 是在写盘这一步才发现的)。\n * 这是刻意的:追加发生在 `unavailable.json` 落盘之前,且调用方随后用它的长度报摘要——\n * 就地追加让产物与摘要两边同时正确。\n */\nexport function writeCrawlProducts(\n layout: ReconLayout,\n modelsBody: Record<string, unknown>,\n metas: Record<string, Record<string, unknown>>,\n unavailable: UnavailableEntry[],\n metaType: string = DEFAULT_META_TYPE,\n): Record<string, string> {\n const written: Record<string, string> = {};\n layout.writeJson('models.json', modelsBody);\n written.models = 'models.json';\n\n for (const [key, body] of Object.entries(metas)) {\n try {\n layout.writeJson(`meta/${key}.${metaType}.json`, body);\n } catch (e) {\n if (!(e instanceof LayoutContainmentError)) throw e;\n // 🔴 modelKey 是现爬的,可能含 `..`。落盘闸**硬失败**而不是把\n // `meta/../models.json` 静默写成产物根下的 models.json 顶掉模型名册。\n // 这里必须把抛出来的那一档转成一条**显式产物**——否则这个模型就从基线里凭空消失了,\n // 而「没写下来」和「这个模型不存在」同形。\n unavailable.push({\n modelKey: key,\n metaType,\n status: 200,\n error: 'unsafe-model-key',\n note: e.message,\n });\n }\n }\n written.meta = 'meta/';\n\n // 「爬过且为空」必须是一个**显式产物**:crawled=true + count=0。\n // reconciliation 一并落盘:顾问手上只有磁盘上这几个文件,没有跑这轮爬取的进程。\n layout.writeJson('unavailable.json', {\n crawled: true,\n count: unavailable.length,\n entries: unavailable,\n reconciliation: reconcileUnavailable(modelsBody, unavailable),\n });\n written.unavailable = 'unavailable.json';\n return written;\n}\n","/**\n * 把爬下来的 meta 摊成**一行一字段**的 `index/fields.jsonl`(契约清单 §3.2,21 条)。\n *\n * 这份产物就是检索面的全部:顾问在一台没有代码仓库、没有安装包的机器上,\n * 只能靠 `grep 部门 index/fields.jsonl` 把一个中文说法落到 `{model, key, type, enums}`。\n * 所以这里每一条纪律都直指「grep 得到 / grep 不到」,而不是「结构好不好看」。\n *\n * 🔴 **没有静默丢弃**:摊不进索引的字段一律进 `dropped` 台账并返回给调用方。\n * 本模块与两个兄弟收集器同形(`crawlMeta → {metas, unavailable}`、\n * `collectActionGates → {gates, coverage}`):三者都把「被丢了什么」和「收到了什么」一起交出来。\n */\n\n/**\n * 🔴 凡是**工具自己合成**、产品从没发过的取值,必须一眼看得出是我们补的。\n * 没有这个标记时,`{label:\"在职\"}`(value 是我们编的)与\n * `{value:\"在职\",label:\"在职\"}`(服务端真发的)产出**逐字相同**,\n * 顾问会拿一个我们编的值去拼筛选条件。\n */\nexport const KIT_VALUE_SOURCE = '_kitValueSource';\n\nexport interface EnumRow {\n value: string;\n label: string;\n [KIT_VALUE_SOURCE]?: string;\n}\n\nexport interface FieldRow {\n model: string;\n key: string;\n name: string;\n type: string;\n isRequired: boolean;\n isFlex: boolean;\n columnDefinition: string;\n synonyms: string[];\n description: string;\n enums: EnumRow[];\n}\n\nfunction typeName(v: unknown): string {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'list';\n return typeof v === 'object' ? 'dict' : typeof v;\n}\n\n/**\n * 判读一份 meta 载荷:返回 `{fields, problem}`,`problem` 是人话描述(undefined = 正常)。\n *\n * 🔴 **这是 `fields` 与 `modelsmd` 的唯一判读口径**。两个消费者吃的是**同一份 metas**\n * (runner 一次爬取喂给两边)——各写各的判读,就会出现「一边记台账继续跑、\n * 另一边裸抛异常」这种半容忍状态,而它只在客户现场那一次才暴露,\n * 那里恰恰**没有源码也没有栈可读**。\n *\n * 🔴 「没有 `fields` 键」与「`fields` 显式是 null」是**两个不同的事实**:\n * 前者是「这个模型确实没字段」(正常,不进台账);后者是服务端发了个形状异常的载荷,\n * 与发 `{}` / 发字符串同级,必须留痕。不单独判就会被静默当成「没有字段」,\n * 失败方向是**低报**。\n */\nexport function metaFields(body: unknown): { fields: unknown[]; problem?: string } {\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return { fields: [], problem: `meta 载荷不是对象(${typeName(body)})` };\n }\n const obj = body as Record<string, unknown>;\n if (!Object.prototype.hasOwnProperty.call(obj, 'fields')) return { fields: [] };\n const fields = obj.fields;\n if (fields === null) {\n return { fields: [], problem: 'fields 是 null(服务端显式发了 null,与「没有 fields 键」不是一回事)' };\n }\n if (!Array.isArray(fields)) return { fields: [], problem: `fields 不是数组(${typeName(fields)})` };\n return { fields };\n}\n\n/**\n * 枚举取 `enums`(**不是 `options`**),每项保留 `value` + `label`。\n *\n * 🔴 2026-08-16 实测(8087,147 个模型 / 2676 个字段):字段级键里**根本没有 `options`**,\n * 枚举挂在 `enums` 上(120 个字段有,占 4%)。元素形态**有三种**\n * (`{key,label,value}`×329、`{key,label,type,value}`×83、`{effect,key,label,type,value}`×28)\n * ⇒ **不许按固定形状解包**:只取需要的两个键,多余的忽略。\n * label 一起留:顾问搜的是「已离职」这种**业务话术**,不是 `TERMINATED`。\n *\n * 🔴 **认不出的元素也留下,一个都不丢**。因果要写对:「现网 440 个元素 100% 带 value」\n * 是**运气**,不是免疫——它挡不住 number / null / 只有 label 的对象,\n * 而这几种一旦出现,按固定形状解包会让整条枚举**无痕消失**,产物看起来却完全正常。\n *\n * 🔴 **但补出来的东西必须认领**:退化路径产出的行带 `_kitValueSource`。\n * 少了它,三种互不相同的输入(元素是 null / 是裸串 \"null\" / 是 `{value:\"null\"}`)\n * 产出**逐字相同**,而其中两种的 value 是我们编的。\n * 服务端真发了 value 的行**不带**这个键:干净的那条路必须保持干净。\n */\nexport function enumRows(field: Record<string, unknown>): EnumRow[] {\n const items = field.enums;\n if (!Array.isArray(items)) return [];\n const out: EnumRow[] = [];\n for (const o of items) {\n if (o && typeof o === 'object' && !Array.isArray(o)) {\n const rec = o as Record<string, unknown>;\n const value = rec.value;\n const label = rec.label;\n if (value !== undefined && value !== null) {\n // 产品自己发的取值:不加任何标记。\n out.push({ value: String(value), label: String(label ?? '') });\n } else if (label !== undefined && label !== null) {\n // 缺 value 的对象:value 退回 label,至少让这条枚举还能被中文 grep 到。\n out.push({ value: String(label), label: String(label), [KIT_VALUE_SOURCE]: 'label' });\n } else {\n out.push({ value: JSON.stringify(o), label: '', [KIT_VALUE_SOURCE]: 'repr:dict' });\n }\n continue;\n }\n // 非对象(裸标量 / number / null …):整体字符串化留痕,并把**原始类型**记进标记——\n // 否则 `null` 与裸串 `\"null\"` 在产物里同形。\n // ⚠️ 这些形态**未在现网观测到**(实测 440 个元素全是对象),留这一支是为了「认不出也别丢」。\n out.push({ value: String(o), label: '', [KIT_VALUE_SOURCE]: `element:${typeName(o)}` });\n }\n return out;\n}\n\nexport interface FieldExtraction {\n rows: FieldRow[];\n /** 人话描述的台账,如 `\"ModelX: 第 3 个字段没有可用的 key(null)\"`。 */\n dropped: string[];\n}\n\n/**\n * 摊平。\n *\n * 🔴 `dropped` 不能只被「记下来」——没有落点的台账等于没记。\n * 它的两处落点是 `models.md` 的警告段与 runner 摘要,缺一不可。\n */\nexport function extractFieldRows(metas: Record<string, unknown>): FieldExtraction {\n const rows: FieldRow[] = [];\n const dropped: string[] = [];\n for (const [model, body] of Object.entries(metas)) {\n // 判读走 `metaFields` 单一口径:渲染侧吃同一份 metas,两边不许各判各的。\n const { fields, problem } = metaFields(body);\n if (problem) {\n // 前提由 `crawlMeta` 保住(只有对象载荷才进 metas),但**保不住别的喂法**:\n // 从磁盘 `meta/*.json` 直接读回来的调用方不受那条前提约束。留台账,不崩也不静默。\n dropped.push(`${model}: ${problem},整个模型没有字段进索引`);\n continue;\n }\n fields.forEach((f, index) => {\n const i = index + 1;\n if (!f || typeof f !== 'object' || Array.isArray(f)) {\n dropped.push(`${model}: 第 ${i} 个字段不是对象(${typeName(f)})`);\n return;\n }\n const rec = f as Record<string, unknown>;\n const key = rec.key;\n if (typeof key !== 'string' || !key) {\n // 没有 key 的字段既不能被调用、也无法与产品侧对账——但它消失这件事必须看得见。\n dropped.push(`${model}: 第 ${i} 个字段没有可用的 key(${JSON.stringify(key) ?? String(key)})`);\n return;\n }\n rows.push({\n model,\n key,\n // 🔴 中文名的键是 `name`,**不是 `label`**(实测 2676/2676 个字段没有一个带 label)。\n // 本仓字段命名规范也是「名称字段统一用 name,禁止 label」。\n name: str(rec.name),\n type: str(rec.type),\n // 🔴 必填的键是 `isRequired`,**不是 `required`**(实测 required 出现 0 次)。\n // 缺省一律显式 false,不用 null——null 会把「没这属性」和「值是假」混成一件事。\n isRequired: Boolean(rec.isRequired ?? false),\n isFlex: Boolean(rec.isFlex ?? false),\n // `columnDefinition` 是实测存在的存储侧信息(14% 的字段有);原计划的 `storage` 键**不存在**。\n columnDefinition: str(rec.columnDefinition),\n // 🔴 顾问真正会 grep 的是业务话术:字段级 synonyms 与 description。\n // 它们稀疏但**恒发**(空就是 [] / \"\")——「有这一列且为空」与「没这一列」是两个值。\n synonyms: Array.isArray(rec.synonyms) ? rec.synonyms.filter(Boolean).map(String) : [],\n description: str(rec.description),\n enums: enumRows(rec),\n });\n });\n }\n return { rows, dropped };\n}\n\nfunction str(v: unknown): string {\n return v === undefined || v === null || v === false ? '' : String(v);\n}\n\n/** 索引落在 `find` 将要读的那个路径上。 */\nexport function writeFieldsIndex(\n layout: { writeJsonl(rel: string, rows: Iterable<unknown>): string },\n rows: FieldRow[],\n): string {\n // 走 layout.writeJsonl:不转义非 ASCII 收口在那一处——\n // 中文被转义成 \\uXXXX 就等于整个检索层失效。\n // 签名只吃 rows:台账的落点是 models.md 与 runner 摘要,不混进 JSONL 行里。\n return layout.writeJsonl('index/fields.jsonl', rows);\n}\n","/**\n * 只读闸(action gates)的爬取与落盘(契约清单 §3.5,29 条)。\n *\n * `readOnly` 是产品里**唯一**让 AI 工具 `hcm_invoke_read_action` 免用户确认直接执行的开关。\n * 判错的代价不是「基线不准」,是 **AI 静默写客户生产库**。所以本模块一切从严、一切 fail-closed:\n * 凡是没能确凿看到「显式声明只读 + 全部候选都只读 + 端点自己也不认为有分歧」的条目,一律不进闸表。\n */\nimport type { Outcome, ReconProbe } from '../probe';\nimport type { ReconLayout } from '../layout';\nimport { sequentialGet } from './crawl';\n// 🔴 复用 §3.1 的分类 note 表而不是抄一份:两份表必然漂移,而顾问在现场唯一能照着做的\n// 分流判据就写在这些 note 里。/actions 与 /meta 的失败成因是同一套 —— 2026-08-16 在 8087/租户 13\n// 打满 **761 个 key 双向对照**:meta 非 200 = 19、actions 非 200 = 19,且两个方向的差集都为空,\n// 即两边的不可用集合**集合相等**(不是「计数相等」—— 计数相等推不出集合相等,别把它读成同一句话)。\nimport { noteFor } from './notes';\n\n/** 🔴 措辞纪律:只能说「在声明影响面内未观测到变化」,不许说「证明无副作用」。 */\nexport const WORDING =\n '本文件登记的是**显式声明**为只读的动作。kit 只能走 RBAC 裁剪后的 REST、' +\n '观测窗口有限,因此任何结论的口径都是「在声明影响面内未观测到变化」,' +\n '而**不是**「无副作用」。guard=declared-only 的条目连服务端静态守卫都没有,' +\n '只有一句声明。';\n\n// guard 二值。**没有第三档**:任何「看起来也挺安全」的中间态都会变成放行的理由。\n/** source 全部是 java ⇒ `ReadOnlyActionIsReallyReadOnlyTest` 守着。 */\nexport const GUARD_ARCH = 'declared+arch-guard';\n/** 其余 ⇒ 只有一句声明,无服务端静态守卫。 */\nexport const GUARD_DECLARED_ONLY = 'declared-only';\nexport const ARCH_GUARDED_SOURCE = 'java';\n\n/**\n * 「声明了只读、却被 fail-closed 规则拒掉」的动作必须留痕:不留痕的话,\n * 「我们拒了它」与「压根没这个动作」在产物里同形,下一个人无从复核我们拒得对不对。\n */\nexport type RejectReason =\n | 'conflict'\n | 'conflict-undeclared'\n | 'candidate-not-read-only'\n | 'no-candidates'\n | 'malformed-candidate'\n | 'malformed-key'\n | 'malformed-action';\n\n/**\n * 🔴 键是 `RejectReason` 联合而不是 `string`:`Record<string, string>` 下\n * `REJECT_NOTES[reason]` 的类型是 `string | undefined`,一个拼错的 reason 会在产物里\n * 落成 `\"note\": undefined`(该键直接消失)。收成联合让拼错在**编译期**就红。\n */\nexport const REJECT_NOTES: Record<RejectReason, string> = {\n conflict:\n '端点标了 conflict=true:同 key 的候选彼此不一致。不一致就不能免确认执行 —— ' +\n '这正是「为 A 放行、执行 B」那条洞',\n 'conflict-undeclared':\n '端点没有明确说 conflict=false(键缺失或不是布尔)。fail-closed 不猜:' +\n '缺声明按有分歧处理。与 readOnly 同一条纪律 —— 缺键不等于「没问题」',\n 'candidate-not-read-only':\n '聚合说只读,但至少一个候选没有显式 readOnly=true。' +\n '多候选聚合是 all 不是 any:一个只读候选不能把会写库的动作抬成闸',\n 'no-candidates': '声明了 readOnly=true 却没有任何候选背书。没有候选 = 没有任何东西可核',\n 'malformed-candidate': '候选不是对象(网关改写 / 版本漂移?),无法核对它的 source 与 readOnly',\n 'malformed-key': '动作没有可用的 key —— 没有 key 就无法调用,也无法与产品侧对账',\n 'malformed-action': '动作条目不是对象,无法判读它是不是只读闸',\n};\n\nexport interface Gate {\n model: string;\n key: string;\n /**\n * action 级的中文名。\n *\n * 🔴 **必须显式兜成 `null`,不能留 `undefined`**:这是 Python → TS 的一个真陷阱。\n * Python 的 `action.get(\"label\")` 缺键时给 `None`,落 JSON 是 `\"label\": null`;\n * TS 里 `action.label` 缺键是 `undefined`,而 `JSON.stringify` 会把它**整个键删掉** ——\n * 产物里的 gate 条目从 9 键变成 8 键,「闸条目形状稳定」那条契约就此静默失效。\n */\n label: string | null;\n source: string[];\n state: string | null;\n readOnly: true;\n conflict: false;\n candidates: unknown[];\n guard: string;\n}\n\nexport interface RejectedEntry {\n model: string;\n key: string | null;\n reason: RejectReason;\n note: string;\n}\n\nexport interface GateUnavailable {\n modelKey: string;\n status: number;\n error: string;\n note: string;\n bodyPreview?: string;\n}\n\nexport interface GateCoverage {\n scope: 'state-scoped';\n state: string | null;\n statesEchoed: string[];\n stateObserved: boolean;\n stateEchoAgrees: boolean;\n stateRequested: string;\n modelsWithoutStateEcho: number;\n sources: string[];\n modelsQueried: number;\n duplicateModelKeys: number;\n modelsAnswered: number;\n modelsUnavailable: number;\n accountingBalanced: boolean;\n unavailable: GateUnavailable[];\n rejected: RejectedEntry[];\n endpointGates: false;\n note: string;\n}\n\n/**\n * `/api/models/{k}/actions`,带 state 时缀查询串。\n *\n * 模型名不做 URL 转义(与 `metaPath` 同规矩):转义会让产物不再能按 modelKey grep 回去,\n * 而畸形 URL 由探针归成 status=-1 的 bad-response。\n * **state 是必须转义的** —— 它是顾问随手敲进来的值,一个空格或 `&` 就能把请求打歪\n * (且假探针的前缀路由看不出来,只有断实际 URL 的用例逮得住)。\n */\nexport function actionsPath(modelKey: string, state = ''): string {\n const base = `/api/models/${modelKey}/actions`;\n if (!state) return base;\n return `${base}?${new URLSearchParams({ state }).toString()}`;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\n\nfunction reject(out: RejectedEntry[], model: string, key: unknown, reason: RejectReason): void {\n out.push({\n model,\n key: typeof key === 'string' && key ? key : null,\n reason,\n note: REJECT_NOTES[reason],\n });\n}\n\nexport interface GateResult {\n gates: Gate[];\n coverage: GateCoverage;\n}\n\n/**\n * 返回 `{gates, coverage}`。只收 `readOnly === true` 且过得了全部 fail-closed 关的条目。\n *\n * gate 条目是 **9 键**:`model` / `key` / `label` / `source` / `state` / `readOnly` /\n * `conflict` / `candidates` / `guard`。`label` 是 action 级的中文名(meta 的**字段**级叫\n * `name`,两者不同源,别统一),它是「本地可 grep 的基线」这句承诺在闸表这一块的落点。\n */\nexport async function collectActionGates(\n probe: ReconProbe,\n modelKeys: string[],\n state = '',\n): Promise<GateResult> {\n // 去重保序:重复的 key 会让「扫了 N 个模型」与实际请求数对不上,\n // 而 modelsQueried 恰恰是下游用来判断覆盖面的那个数。\n const keys = [...new Set(modelKeys)];\n const paths = new Map<string, string>();\n for (const k of keys) paths.set(actionsPath(k, state), k);\n const results: Record<string, Outcome> = probe.getMany\n ? await probe.getMany([...paths.keys()])\n : await sequentialGet(probe, [...paths.keys()]);\n\n const gates: Gate[] = [];\n const rejected: RejectedEntry[] = [];\n const unavailable: GateUnavailable[] = [];\n const sources = new Set<string>();\n const statesEchoed = new Set<string>();\n let noStateEcho = 0;\n let answered = 0;\n\n for (const [path, model] of paths) {\n const out = results[path];\n if (out === undefined) {\n // 并发丢任务:必须显式记,否则表现成「这个模型没有只读闸」。\n unavailable.push({\n modelKey: model,\n status: -1,\n error: 'not-crawled',\n note: '并发批次里没有返回该 path',\n });\n continue;\n }\n if (out.error !== 'ok') {\n unavailable.push({ modelKey: model, status: out.status, error: out.error, note: noteFor(out.error) });\n continue;\n }\n const body = out.body;\n if (!isRecord(body) || !Array.isArray(body.actions)) {\n // 🔴 HTTP 成功 ≠ 扫成了。载荷不是对象、或根本没有 actions 数组时,\n // 我们**不知道**这个模型有没有只读闸 —— 记成 answered 就等于替它宣布「没有闸」。\n // 注意与 `\"actions\": []` 的区别:那是爬过且确实为空,算 answered。\n unavailable.push({\n modelKey: model,\n status: out.status,\n error: 'unexpected-shape',\n note:\n `HTTP ${out.status} 成功但载荷是 ${body === null ? 'null' : Array.isArray(body) ? 'list' : typeof body}` +\n '、或其中没有 actions 数组,无法判读该模型的只读闸' +\n '(载荷形状问题,不是取数失败)',\n bodyPreview: JSON.stringify(body).slice(0, 200),\n });\n continue;\n }\n\n answered += 1;\n // 端点回显的 state 才是这条记录的覆盖面;我们请求的那个只是意图,两者不一定相等。\n const echoed = typeof body.state === 'string' ? body.state : null;\n if (echoed === null) noStateEcho += 1;\n else statesEchoed.add(echoed);\n\n for (const action of body.actions) {\n if (!isRecord(action)) {\n reject(rejected, model, null, 'malformed-action');\n continue;\n }\n const cands: unknown[] = Array.isArray(action.candidates) ? action.candidates : [];\n for (const c of cands) {\n if (isRecord(c) && typeof c.source === 'string' && c.source) sources.add(c.source);\n }\n\n // fail-closed 第一关:没有**显式** readOnly=true 的一律按写动作对待,\n // 无论它叫 preview / check / test。缺键不是「默认只读」。\n if (action.readOnly !== true) continue;\n\n const key = action.key;\n if (typeof key !== 'string' || !key) {\n reject(rejected, model, key, 'malformed-key');\n continue;\n }\n // 🔴 要的是**明确的** conflict=false,不是「没标 true 就算没分歧」——\n // 与上面 readOnly 那关同一条纪律:缺键不等于「没问题」。\n if (action.conflict !== false) {\n reject(rejected, model, key, action.conflict === true ? 'conflict' : 'conflict-undeclared');\n continue;\n }\n if (cands.length === 0) {\n // `[].every()` 恒为 true —— 零候选会从下面那关直接漂过去,必须先拦。\n reject(rejected, model, key, 'no-candidates');\n continue;\n }\n if (!cands.every((c) => isRecord(c))) {\n reject(rejected, model, key, 'malformed-candidate');\n continue;\n }\n if (!cands.every((c) => (c as Record<string, unknown>).readOnly === true)) {\n // 🔴 聚合是 all 不是 any:一个只读候选不能把会写库的 action 抬成闸。\n reject(rejected, model, key, 'candidate-not-read-only');\n continue;\n }\n\n // 🔴 逐个候选比 source,不走 Set 去重。危险的不是 Set 本身,而是 **Set + 过滤**:\n // - 朴素 `new Set(cands.map(c => c.source))`:缺 source 的候选贡献一个 `undefined`\n // ⇒ 集合是 {\"java\", undefined} ⇒ 仍判 declared-only,**不会**误升;\n // - 但下面那行 `source` 字段为了产出干净列表**带了 typeof 过滤**。同一套过滤逻辑\n // 一旦被复用到 guard 判定上,缺 source 的候选就被整个滤没了 ⇒ 集合恰好等于 {\"java\"}\n // ⇒ 误升成 arch-guard。逐个比不吃这一套:没有 source 的候选一票否决。\n const allJava = cands.every((c) => (c as Record<string, unknown>).source === ARCH_GUARDED_SOURCE);\n gates.push({\n model,\n key,\n // 缺 label 兜成 null,见 `Gate.label` 上那条注释(undefined 会让键整个消失)。\n label: typeof action.label === 'string' ? action.label : null,\n source: [\n ...new Set(\n cands\n .map((c) => (c as Record<string, unknown>).source)\n .filter((s): s is string => typeof s === 'string' && Boolean(s)),\n ),\n ].sort(),\n state: echoed,\n readOnly: true,\n conflict: false,\n candidates: cands,\n guard: allJava ? GUARD_ARCH : GUARD_DECLARED_ONLY,\n });\n }\n }\n\n const echoedList = [...statesEchoed].sort();\n const coverage: GateCoverage = {\n // 🔴 永远是 state-scoped:产品没有「列出可用 state」的端点(实测约 200 个取值),\n // 「全集」不是一个可兑现的词,所以本产物只覆盖本次请求的那一个 state。\n scope: 'state-scoped',\n // 端点回显的 state。恰好一个取值时记那个值;多个模型回显不一致时记 null 并把\n // 全部取值摊在 statesEchoed 里 —— 取最后一个赢家会让产物声称一个并不成立的覆盖面。\n state: echoedList.length === 1 ? (echoedList[0] as string) : null,\n statesEchoed: echoedList,\n // 🔴 「回显了空 state」(state=\"\") 与「一个模型都没答上来」是两回事,\n // 不许靠 null 一个值兼职两种含义。\n stateObserved: statesEchoed.size > 0,\n stateEchoAgrees: statesEchoed.size <= 1,\n stateRequested: state,\n modelsWithoutStateEcho: noStateEcho,\n sources: [...sources].sort(),\n modelsQueried: keys.length,\n duplicateModelKeys: modelKeys.length - keys.length,\n modelsAnswered: answered,\n modelsUnavailable: unavailable.length,\n // 前提显式断言:不平就说明有模型既没记成答上来、也没记成失败 —— 「扫了 N 个模型」不成立。\n accountingBalanced: keys.length === answered + unavailable.length,\n unavailable,\n rejected,\n endpointGates: false, // 普通 Controller 端点闸不在本产物内\n note:\n 'scope 恒为 state-scoped:state 不可枚举,本产物只覆盖 stateRequested 请求到的那一个;' +\n 'unavailable = 没扫成的模型(与「这个模型没有只读闸」是两回事);' +\n 'rejected = 声明了只读却被 fail-closed 规则拒掉的动作,它们不是闸;' +\n // 🔴 口径必须写死在产物里:sources 是**全体** action 的候选来源,不是闸的来源。\n // 少这一句,下游拿 sources 不等于 [\"java\"] 当「出现了非 Java 只读闸」的信号就会误判。\n 'sources = 本轮见到的**全部** action(含写动作、含被 rejected 的条目)的候选 source 去重,' +\n '**不是**闸的来源;闸自己的来源看每条 gate 的 source 字段',\n };\n return { gates, coverage };\n}\n\nexport function writeGates(layout: ReconLayout, gates: Gate[], coverage: GateCoverage): string {\n // 「爬过且为空」必须是显式产物:crawled=true + count=0,与「没爬过」(文件不存在)区分开。\n return layout.writeJson('gates.json', {\n wording: WORDING,\n coverage,\n crawled: true,\n count: gates.length,\n gates,\n });\n}\n","/**\n * 强连通分量(Tarjan)。\n *\n * 🔴 **独立成模块,不是为了「分层好看」**:`topology` 里那条「缩点图还有环」的前提自证\n * 在正确实现下**不可达**(缩点图恒为 DAG),唯一能证明它还活着的办法就是短路 SCC 计算。\n * 而模块内的私有函数在 ESM 下换不掉 —— 拆出来之后,测试用 `vi.mock` 替换本模块即可,\n * **生产代码里不必开任何注入口子**(那种为测试而生的扩展点正是 YAGNI 要挡的)。\n */\n\n/**\n * 数组字典序比较。\n *\n * 🔴 **不能用默认 `.sort()`**:JS 对数组元素排序时走 `String(a)`,\n * `['a','b']` 会被拍成 `\"a,b\"` —— 分隔符 `,`(44) 参与比较,与 Python 的\n * 元素逐个比较**不等价**(`['a,b']` 与 `['a','b']` 在 JS 里同形)。\n * 产物要跨语言逐字可 diff,比较必须显式写出来。\n */\nexport function compareStringArrays(a: string[], b: string[]): number {\n for (let i = 0; i < Math.min(a.length, b.length); i += 1) {\n if (a[i]! < b[i]!) return -1;\n if (a[i]! > b[i]!) return 1;\n }\n return a.length - b.length;\n}\n\n/**\n * 迭代版 Tarjan SCC,返回**全部**分量(含单点),每个分量内部已排序。\n *\n * 用迭代而不是递归 —— 但**把话说准**:实测这张图在本模块的遍历序(根节点排序、邻居排序)下\n * DFS **最深只有 6 层**,最长依赖链 12 个模型,离栈上限很远,递归版在**这份数据上不会**炸。\n * ⚠️ 那个 6 是**这个遍历序下**测到的,换个根序能走得更深,真正的上界是节点数(今天 742)。\n * 迭代版买的就是这个上界:越线时递归版的失败形态是栈溢出裸抛,\n * 顾问看到的是一屏 traceback 不是人话。\n *\n * 🔴 邻居按排序遍历:`adj` 的值是 Set,迭代序是插入序而非字典序。\n * 不排序时**分量的产出顺序**会随输入顺序变,而**分量集合**不变 —— 结果对、中间态不稳,\n * 调试时两次跑同一份输入看到不同中间态,会把人引去查根本不存在的不确定性。\n */\nexport function tarjan(nodes: string[], adj: Map<string, Set<string>>): string[][] {\n const index = new Map<string, number>();\n const low = new Map<string, number>();\n const onStack = new Set<string>();\n const stack: string[] = [];\n const result: string[][] = [];\n let counter = 0;\n const neighbours = (n: string): string[] => [...(adj.get(n) ?? [])].sort();\n\n for (const root of nodes) {\n if (index.has(root)) continue;\n index.set(root, counter);\n low.set(root, counter);\n counter += 1;\n stack.push(root);\n onStack.add(root);\n const work: Array<{ node: string; it: string[]; pos: number }> = [\n { node: root, it: neighbours(root), pos: 0 },\n ];\n while (work.length > 0) {\n const frame = work[work.length - 1]!;\n let advanced = false;\n while (frame.pos < frame.it.length) {\n const nxt = frame.it[frame.pos]!;\n frame.pos += 1;\n if (!index.has(nxt)) {\n index.set(nxt, counter);\n low.set(nxt, counter);\n counter += 1;\n stack.push(nxt);\n onStack.add(nxt);\n // 🔴 迭代版的关键:把「递归下去」换成「压栈后 break」,\n // 父节点的剩余邻居留在 `pos` 里,回来接着走。\n // 每次恢复都从头重来的话结果仍然对,但同一批邻居会被反复重走。\n work.push({ node: nxt, it: neighbours(nxt), pos: 0 });\n advanced = true;\n break;\n }\n if (onStack.has(nxt)) {\n // 回边 / 横叉边指向仍在栈上的节点:取它的 index(不是 low)。\n low.set(frame.node, Math.min(low.get(frame.node)!, index.get(nxt)!));\n }\n }\n if (advanced) continue;\n work.pop();\n if (work.length > 0) {\n // 子节点走完,把它的 low 回传给父节点(等价于递归版返回后的那一行)。\n const parent = work[work.length - 1]!.node;\n low.set(parent, Math.min(low.get(parent)!, low.get(frame.node)!));\n }\n if (low.get(frame.node) === index.get(frame.node)) {\n const comp: string[] = [];\n for (;;) {\n const w = stack.pop()!;\n onStack.delete(w);\n comp.push(w);\n if (w === frame.node) break;\n }\n result.push(comp.sort());\n }\n }\n }\n return result;\n}\n\n","/**\n * 模型级依赖图(契约清单 §3.7,33 条)。回答的是「先建哪个模型」,**不是**「先建哪一行」。\n *\n * 🔴 边界:模型级不足以定行级顺序。组织自环只说明「这张表内部有父子序」,\n * 决定不了具体哪一行先写 —— 行级 DAG 是后续刀的 `work/import-plan.json`。\n * 本文件如实标出自环,但**不**假装它能排行。\n *\n * 🔴 边只从 `refEdges` 来(本模块不再判读 `object` / `refer`):两处各判一次\n * 就会长出「一边算 2929 条、另一边算 2926 条」这种谁也说不清的差额。\n *\n * 🔴 **环的下游必须点名**:把「指向环成员的依赖」直接抹掉再分波,被抹掉依赖的模型会落进\n * `waves[0]`,而顾问读 `waves[0]` 就是「可以第一个导」。照错的那份导,结果是引用不存在行的 400。\n * ⇒ 分波跑在**缩点图**(每个强连通分量缩成一个点)上,环成员照旧不进 `waves`,\n * 但它们占的那一层是**实打实占掉的**。\n */\nimport type { ReconLayout } from '../layout';\nimport type { RefEdge, DanglingRef } from './refs';\nimport { compareStringArrays, tarjan } from './scc';\n\n/**\n * 🔴 依据是**字段名约定**,不是产品声明 —— 实测这 1476 个字段的\n * isReadonly/autoGenerate/isList/isInfo 全为 false,元数据里没有任何可编程判据能识别它们。\n * 所以「审计字段是服务端填的」这个**理由**我们证不了,能证的只有**效果**:\n * 见每次运行现算的 `excludedEdges.impact`。\n *\n * 🔴 这已经是**跨产物契约**:`mapping.ts` 也 import 它,改这里会同时移动\n * `topology.json` 与 `mapping/` 两份产物。\n */\nexport const AUDIT_FIELD_KEYS = ['createById', 'updateById'] as const;\nconst AUDIT_SET = new Set<string>(AUDIT_FIELD_KEYS);\n\nexport const WORDING =\n // 🔴 这一句必须在最前面:下面每个键都是**在这张裁剪过的图上**算出来的。\n // 少了它,顾问拿 cycles=[] 会读成「产品里没有互相引用」,而实际是「排掉审计边之后没有」。\n '🔴 全部结论都算在**排除 excludedEdges(审计边)之后**的边集上 —— ' +\n 'cycles / selfLoops / waves / blockedByCycles 无一例外;' +\n 'waves = 模型级拓扑分波,跑在缩点图上,同一波内部可并行,覆盖「全部模型 − unordered」不重不漏;' +\n // 🔴 这里**不能**写成「waves[0] 不依赖任何其他模型」。整个第 0 层都是环分量时,\n // 那一层不出现在 waves 里,waves[0] 于是可能整层都依赖环成员(A→B, B↔C 即是)。\n 'waves[0] 不依赖任何**可定序**模型;' +\n '🔴 但 waves 里有位置**不等于「可以直接导」**:凡是同时出现在 blockedByCycles 里的模型,' +\n '都还依赖着环成员,**必须先把 cycles 按两段式解开**才能导 —— 读 waves 必须连 blockedByCycles 一起读;' +\n 'cycles = 强连通分量(≥2 个模型互相依赖),[] 表示**算过且没有**,不是没算;' +\n 'selfLoops = 自环模型(如组织的 parentId),自环不进 cycles —— 它说明**行级**有父子序,' +\n '模型级并不阻塞,照样进 waves;' +\n 'unordered = 卷进 cycles 因而定不出顺序的模型,它们**不在** waves 里,' +\n '把它们塞进 waves 等于假装定得出顺序;' +\n 'blockedByCycles = 自己不在环里、但(传递地)依赖某个环成员的模型;' +\n 'excludedEdges = 按字段名约定(createById / updateById)排除出依赖图的审计边,原样留着可放回,' +\n '🔴 这条口径在**本次数据上**值多少,看 excludedEdges.impact(含/不含审计边各现算一遍,' +\n '不是任何一个租户的历史数字);' +\n 'edgesOutsideNodes = 两端有一头不在本次模型名册里的边,不进图但也不静默丢,同样原样留着;' +\n 'dangling = 产品声明了引用、但这个引用用不了的字段,**两档,🔴 权威判据是 reason 不是 toModel**:' +\n 'reason=not-in-registry 时 toModel 是**真的模型名**、只是不在 /api/system/models 名册里' +\n '(实测 4 个:ImpactRecord / JobFamily / PerfAssessDimensionInstance / UserModel),照着填会拿到 400;' +\n 'reason=malformed-target 时产品给的目标形态就是坏的(object={\"a\":1} / 5 / [\"T\"] 之类),' +\n '此时 toModel 存的是 **repr(原值)**、是 kit 合成的字符串,**不是模型名** —— ' +\n '拿它去查模型只会查到一个不存在的名字,该字段根本没有可用的引用目标';\n\nexport const NOTE =\n '模型级顺序 ≠ 行级顺序。树形数据(组织、岗位序列)的行序要看后续的 ' +\n 'work/import-plan.json;本文件只保证「模型 A 的第一行写之前,模型 B 已经写完」。' +\n 'waves 里没有 cycles/unordered 的成员,波次编号也**不给它们留空档** —— ' +\n '整层都是环成员的那一层不会出现在 waves 里,所以 waves[0] 未必是「可以第一个导」的那一批:' +\n '先看它与 blockedByCycles 有没有交集。照 waves 顺序执行前,' +\n '必须先按两段式(先插入、引用列留空,再回填)把 cycles 处理掉。';\n\n/** 🔴 产物里唯一解释「为什么少了这些边」的地方,措辞按证得动/证不动分开写。 */\nconst EXCLUDED_NOTE =\n '这些边按**字段名约定**(createById / updateById)排除出依赖图。' +\n '🔴 依据只有命名约定,**不是产品声明**:实测全库 1476 个这样的字段,' +\n 'isReadonly / autoGenerate / isList / isInfo 四个标志全部为 false,' +\n '元数据里没有任何可编程判据能认出「这是服务端填的审计字段」。' +\n '(旁证:有人在现网 CO_0001 的行上观察到 createById 全是系统哨兵 UUID 000…001,' +\n '强烈提示服务端填充 —— 但那是**他人观察、本工具未复核**,且 **recon 这条路**只读不写,' +\n '**证不了**,所以这里不把它当理由。)' +\n '排除的**效果**是量得到的,且**每次运行现算**:见同级的 impact。' +\n '🔴 判定顺序:审计字段**先判**,早于自环判定与名册判定 —— 所以' +\n '① `User.createById → User` 记在这里而不是 selfLoops(否则等于凭空断言「User 有行级父子序」);' +\n '② 一条 createById 指向名册外目标的边也记在这里,**不会**出现在 edgesOutsideNodes 里。' +\n '🔴 如果某个客户环境真的需要顾问自己填 createById / updateById,' +\n // 🔴 不许写成「键与 edges 逐字相同」:`topology.json` 的**顶层没有 edges 键**。\n '这批边就在同级的 edges 数组里,键与 refs 产出的引用边逐字相同(七键:fromModel / fromField / ' +\n 'fieldName / toModel / carrier / labelField / labelFieldSource),放回依赖图即可 —— 排除不等于丢弃。';\n\nconst OUTSIDE_NOTE =\n '边的一端不在本次模型名册里:不进依赖图(进了会凭空多出一个不存在的节点),' +\n '但也不静默丢 —— 静默丢的形态是 edgeCount 与图里用到的边数对不上,' +\n '而这个差额没有任何人看得出来。同级的 edges 是**原样**的引用边' +\n '(键与 refs 产出的引用边逐字相同,七键),' +\n '哪一端出界,拿它的 fromModel / toModel 与模型名册比一下即知。' +\n '按编排(modelKeys ⊇ metas 的键,且 refEdges 已把「目标不在名册」' +\n '分流进 dangling)这里恒为空,实测 0 条。';\n\n/**\n * 🔴 **参考值,不是本次结果**。留着是为了让读者知道这条口径在一个真实中等规模租户上的量级;\n * 本次运行自己的两组数在 `impact.withAudit` / `impact.withoutAudit` 里,是现算的。\n */\nconst REFERENCE_IMPACT = {\n source: '租户 13 / 742 个模型 / 2923 条边,2026-08-16 实测',\n withAudit: { cycles: 4, cycleMembers: 22, selfLoops: 22, blockedByCycles: 717 },\n withoutAudit: { cycles: 3, cycleMembers: 7, selfLoops: 21, blockedByCycles: 23 },\n note:\n '🔴 这是**参考值,不是本次结果** —— 本次的两组数在同级的 withAudit / withoutAudit 里。' +\n '在那个租户上,含审计边时 742 个模型有 717 个(96%)被环挡住、产物等于没用;' +\n '排除后只剩 23 个。别拿这组数去解释你手上这份产物。',\n};\n\ninterface Analysis {\n selfLoops: string[];\n cycles: string[][];\n unordered: string[];\n waves: string[][];\n blockedByCycles: string[];\n excluded: RefEdge[];\n outside: RefEdge[];\n}\n\n/**\n * 在给定边集上算依赖图结构。\n *\n * `excludeAudit=false` 这一路**只**用来算 `excludedEdges.impact` 的对照组 ——\n * 产物本身永远走 `true`。两条路共用同一段代码是承重的:分开写两份,\n * 「对照组」和「产物」就会各自漂移,而 impact 声称的正是两者的差。\n */\nfunction analyze(edges: RefEdge[], nodes: string[], nodeSet: Set<string>, excludeAudit: boolean): Analysis {\n const deps = new Map<string, Set<string>>(nodes.map((n) => [n, new Set<string>()]));\n const selfLoops = new Set<string>();\n const outside: RefEdge[] = [];\n const excluded: RefEdge[] = [];\n for (const e of edges) {\n const a = e.fromModel;\n const b = e.toModel;\n // 🔴 审计边先判,早于自环与名册判定。理由与后果写在 EXCLUDED_NOTE 里(顺序是产物契约的一部分)。\n if (excludeAudit && AUDIT_SET.has(e.fromField)) {\n excluded.push(e);\n continue;\n }\n if (!nodeSet.has(a) || !nodeSet.has(b)) {\n outside.push(e); // 原样,与 excludedEdges 同一形状:放得回去\n continue;\n }\n if (a === b) {\n selfLoops.add(a);\n // 自环不进 deps —— `deps` 的语义是**跨模型**依赖。\n // ⚠️ 别把这一句读成「少了它自环就排不进波次」:删掉它产物**一字不变**,\n // 因为缩点那一步的 `cj !== ci` 会把分量内部边(含自环)一并落掉。\n // 两处都在是「语义在这里、兜底在那里」,不是「少一处就会错」。\n continue;\n }\n deps.get(a)!.add(b);\n }\n\n const comps = tarjan(nodes, deps);\n const compOf = new Map<string, number>();\n comps.forEach((comp, i) => comp.forEach((m) => compOf.set(m, i)));\n const cyclicComps = new Set<number>(comps.map((c, i) => (c.length > 1 ? i : -1)).filter((i) => i >= 0));\n const cycles = [...cyclicComps].map((i) => comps[i]!).sort(compareStringArrays);\n const unordered = [...cyclicComps].flatMap((i) => comps[i]!).sort();\n const cyclic = new Set(unordered);\n\n // 缩点:分量之间的依赖。同一分量内部的边全部落掉(它们正是环本身)。\n const compDeps = new Map<number, Set<number>>(comps.map((_, i) => [i, new Set<number>()]));\n for (const n of nodes) {\n const ci = compOf.get(n)!;\n for (const d of deps.get(n)!) {\n const cj = compOf.get(d)!;\n if (cj !== ci) compDeps.get(ci)!.add(cj);\n }\n }\n\n // Kahn 分波,跑在**缩点图**上 —— 不是「剔掉环成员之后的子图」。\n // 差别正是承重的那一处:环成员占掉的层是实打实占掉的,\n // 依赖它们的模型因此排在环之后,而不是被抹平成零入度。\n const remaining = new Map<number, Set<number>>([...compDeps].map(([i, v]) => [i, new Set(v)]));\n const levelOf = new Map<number, number>();\n let level = 0;\n // 🔴 **有界**循环,不是 `while (remaining.size)`:每轮至少定下一个分量 ⇒ 上界就是分量数。\n // 这一改是为了让「前提自证块被整块删掉」表现为**红**而不是**挂死** ——\n // while 版本删掉下面那个 throw 之后,ready 恒空、remaining 永不变小,测试会一直转,\n // CI 上的形态是 job 超时、零信号。\n for (let round = 0; round <= comps.length; round += 1) {\n if (remaining.size === 0) break;\n const ready = [...remaining].filter(([, d]) => d.size === 0).map(([i]) => i);\n if (ready.length === 0) {\n // 前提自证:缩点图恒为 DAG,这里不该卡住。真卡住只有一种解释 ——\n // `tarjan` 没把互相依赖的模型收进同一个分量(SCC 算错)。\n // 报出来,不要静默丢模型:丢掉的形态是产物看着完整、实际少几个模型。\n const stuck = [...remaining.keys()].map((i) => comps[i]!).sort(compareStringArrays);\n throw new Error(\n '拓扑分波卡住:缩点图里仍然存在环,说明 tarjan 给出的分量不是强连通分量' +\n `(互相依赖的模型没被收进同一个分量)。剩余分量:${JSON.stringify(stuck)}。产物不可信`,\n );\n }\n for (const i of ready) {\n levelOf.set(i, level);\n remaining.delete(i);\n }\n const readySet = new Set(ready);\n for (const d of remaining.values()) for (const r of readySet) d.delete(r);\n level += 1;\n }\n if (remaining.size > 0) {\n // 有界循环的收尾自证:正常路径一定在上界内出清。走到这里说明上面那个 throw 被绕过了。\n // 🔴 措辞与上面那条**故意不同**:两条都喊同一句话时,「主守卫被删掉」会被这条兜底\n // 悄悄接住、测试照样绿。不同措辞让「谁在报」一眼可辨。\n throw new Error(\n `拓扑分波在 ${comps.length + 1} 轮内没有出清,剩余分量 ` +\n `${JSON.stringify([...remaining.keys()].map((i) => comps[i]!).sort(compareStringArrays))};` +\n '分波与 SCC 计算不一致,产物不可信',\n );\n }\n\n // 「被环挡住」= 传递地依赖某个环成员。按层升序推进即可:Kahn 保证依赖方的层严格更小。\n const blockedComps = new Set<number>();\n for (const i of [...comps.keys()].sort((x, y) => levelOf.get(x)! - levelOf.get(y)!)) {\n if ([...compDeps.get(i)!].some((j) => cyclicComps.has(j) || blockedComps.has(j))) blockedComps.add(i);\n }\n\n const buckets = new Map<number, string[]>();\n for (const n of nodes) {\n if (cyclic.has(n)) continue; // 环成员不进 waves:塞进去等于假装定得出顺序\n const lv = levelOf.get(compOf.get(n)!)!;\n if (!buckets.has(lv)) buckets.set(lv, []);\n buckets.get(lv)!.push(n);\n }\n // 整层都是环成员时那一层不出现 —— 「这一波什么都不建」不是可执行的指令。\n // 🔴 代价是 waves[0] 未必零依赖(见 WORDING 里那条 blockedByCycles 的警告)。\n const waves = [...buckets.keys()].sort((a, b) => a - b).map((lv) => buckets.get(lv)!.sort());\n\n return {\n selfLoops: [...selfLoops].sort(),\n cycles,\n unordered,\n waves,\n blockedByCycles: nodes.filter((m) => !cyclic.has(m) && blockedComps.has(compOf.get(m)!)).sort(),\n excluded,\n outside,\n };\n}\n\n/** 把一次 `analyze` 压成四个数 —— `excludedEdges.impact` 的两侧都用它,口径必然一致。 */\nfunction impactOf(r: Analysis): Record<string, number> {\n return {\n cycles: r.cycles.length,\n cycleMembers: r.unordered.length,\n selfLoops: r.selfLoops.length,\n blockedByCycles: r.blockedByCycles.length,\n };\n}\n\nconst NO_META_NOTE =\n '这些模型在名册里、但**本轮没拿到 meta**(多为 HTTP 404 的 `*Index` 型隐藏模型,' +\n '详见 unavailable.json)。⇒ 它们的**出边不可知**:图里看着没有依赖,只是因为' +\n '我们没读到它们的字段。🔴 它们照样在 waves 里(删掉等于假装名册里没这个模型),' +\n '而 waves 里「真的无依赖」与「出边不可知」**同形** —— 照 waves[0] 先导它们,' +\n '可能导到一半才发现有依赖。要判断某个 wave 成员可不可信,' +\n '**先和这份名单取交集**。';\nconst NO_META_UNKNOWN_NOTE =\n '调用方**没告诉**本模块哪些模型拿到了 meta(`modelsWithMeta` 未提供)⇒ ' +\n '本轮无从判断有没有「出边不可知」的节点。`null` 是「不知道」,' +\n '**不是**「一个都没有」—— 后者是 `count: 0` + 空列表。';\n\n/**\n * 从引用边集算模型级依赖图。`edges` / `dangling` 是 `refEdges` 的原样输出。\n *\n * `modelsWithMeta`:**本轮真拿到 meta 的模型 key**。名册里而不在这批里的节点,\n * 它们的出边**不可知** —— 边集是从 metas 算出来的,没有 meta 就一条边都算不出来,\n * 于是它们在 `waves[0]` 里与「爬过且真的无依赖」**完全同形**(8087 实测:761 − 742 = 19 个)。\n * ⚠️ `undefined` = 调用方没告诉我,产物里如实写 `null`;给了就是对过账,缺的那些点名。\n *\n * 🔴 `dangling` **原样透传,一个字段都不解读**。特别是 `dangling[].toModel`:\n * `reason === 'malformed-target'` 那一档存的是原值的字符串化形态(工具合成的字符串),\n * **不是模型名** —— 拿它去查、或塞进节点集,会凭空造出一个叫 `{\"k\":1}` 的模型。\n * 权威判据是 `reason`,而本模块压根不需要判读它。\n */\nexport function buildTopology(\n edges: RefEdge[],\n dangling: DanglingRef[],\n modelKeys: string[],\n modelsWithMeta?: Iterable<string> | null,\n): Record<string, unknown> {\n const nodes = [...new Set(modelKeys)].sort();\n const nodeSet = new Set(nodes);\n\n const kept = analyze(edges, nodes, nodeSet, true);\n // 🔴 对照组**现算**,不写死数字:产物要论证「这次的裁剪值不值」,就得拿**这次的**数说话。\n // 写死任何一个租户的数,客户环境的 topology.json 都会拿别人的数来论证自己的裁剪。\n const withAudit = analyze(edges, nodes, nodeSet, false);\n\n let noMeta: Record<string, unknown>;\n if (modelsWithMeta === undefined || modelsWithMeta === null) {\n noMeta = { count: null, models: null, note: NO_META_UNKNOWN_NOTE };\n } else {\n const seen = new Set(modelsWithMeta);\n const missing = nodes.filter((n) => !seen.has(n));\n noMeta = { count: missing.length, models: missing, note: NO_META_NOTE };\n }\n\n return {\n crawled: true,\n wording: WORDING,\n nodeCount: nodes.length,\n // 🔴 「出边不可知」的节点。恒发键:缺席会被读成「这个工具还不懂这一档」,\n // 而 `null` 与 `[]` 又是两个不同的事实(见上方两条 note)。\n nodesWithoutMeta: noMeta,\n // 🔴 边总数按**传入的边**记,不按图里用掉的边记:两者的差额是\n // excludedEdges + edgesOutsideNodes + 自环,全部摆在同一份产物里才对得上账。\n edgeCount: edges.length,\n selfLoops: kept.selfLoops,\n cycles: kept.cycles,\n waves: kept.waves,\n unordered: kept.unordered,\n blockedByCycles: kept.blockedByCycles,\n excludedEdges: {\n count: kept.excluded.length,\n // 依据写成机器可读的一档,下游要放回去时不必去解析中文。\n basis: 'field-name-convention',\n fieldKeys: [...AUDIT_FIELD_KEYS],\n note: EXCLUDED_NOTE,\n impact: {\n withAudit: impactOf(withAudit),\n withoutAudit: impactOf(kept),\n note:\n '两组数都是**本次现算**的:同一份边集、同一段代码,' +\n '一次含审计边、一次排除,差额就是这条口径在**你手上这份数据**上的代价。' +\n 'reference 是另一个租户的参考值,不是本次结果。',\n reference: REFERENCE_IMPACT,\n },\n // 原样,键与 refEdges 产出的引用边逐字相同(七键)—— 放得回去\n edges: kept.excluded,\n },\n edgesOutsideNodes: {\n count: kept.outside.length,\n note: OUTSIDE_NOTE,\n edges: kept.outside, // 原样,同 excludedEdges 一个形状\n },\n dangling: [...dangling],\n note: NOTE,\n };\n}\n\nexport function writeTopology(layout: ReconLayout, topo: Record<string, unknown>): string {\n return layout.writeJson('topology.json', topo);\n}\n","/**\n * 每模型的可写性画像(契约清单 §3.6,33 条):字段能不能填、填什么类型、值从哪来、\n * 这个模型能不能写。\n *\n * 🔴 `mutation` 这一维如实记录 allowed/reason;`unknown` 留给**没查到**的五种情况\n * (见 `NOT_QUERIED` 上方那张表)。\n * 🔴 **别把这五种都说成「实测为 0」**:底账量到的只有「742 个模型全部有应答、0 失败」\n * 这一条,它覆盖的是**第一种**(没应答)。其余四种**没有实测数据**。\n * 它们与「允许」不许同名,与它们出现过几次无关。\n * ⚠️ 那张表说的是 `collectMutation` 这一侧;`buildMapping` 侧还有三种**调用方喂坏形状**的\n * 成因(见 `mutationSection`),这几种措辞两两不同名。\n *\n * 🔴 **不得**用 `isTree` / `multiple` / `treeLoadMode` 做任何判断:实测这三个键在全部\n * 14299 个字段上分别恒为 `false` / `false` / `\"lazy\"`,是序列化缺省而非信号。\n * 拿它们分支,另一侧夹具根本走不到,守卫看着有、实际是空的。\n *\n * 🔴 **本模块与 `topology` 对审计字段的处置故意不同,别「统一」掉**:\n * `createById` / `updateById` 占指向 `User` 的引用的 94%(1476 / 1564,出现在 738 个模型上)。\n * `topology` 把它们**排除出依赖图** —— 不排除时 742 个模型有 717 个(96%)定不出顺序,\n * 那份产物等于没用。而 mapping 是**字段清单**不是依赖图:它们照常在列,\n * 只在 `ref.auditBackref` 上打一个标记。两处的字段名从 `topology.AUDIT_FIELD_KEYS`\n * 同源取,不各存一份 —— 存两份必然漂移,而漂移的表现是两份产物对不上账、谁也说不清差在哪。\n */\nimport type { Outcome, ReconProbe } from '../probe';\nimport type { ReconLayout } from '../layout';\nimport { LayoutContainmentError } from '../layout';\nimport { sequentialGet } from './crawl';\nimport { metaFields } from './fields';\nimport type { RefEdge } from './refs';\n// 🔴 同源取审计字段名,不抄一份。见模块 docstring:两处处置不同,但**依据必须是同一个**。\nimport { AUDIT_FIELD_KEYS } from './topology';\n// 🔴 与编排层 / valuedomain 同一个键,不抄字面量。\nimport { KIT_CARRY_OVER_KEY } from './carry-over';\n\nconst AUDIT_SET = new Set<string>(AUDIT_FIELD_KEYS);\n\nexport const MUTATION_PATH = (model: string): string => `/api/models/${model}/mutation-capability`;\n\n/**\n * 🔴 `batch_delete` 是 snake_case —— 服务端就是这么返的(实测)。写成 `batchDelete`\n * 会让产物与服务端对不上,而这个手滑极其自然(产物里其余的键几乎都是驼峰)。\n */\nexport const MUTATION_OPS = ['create', 'update', 'delete', 'batch_delete'] as const;\nexport type MutationOp = (typeof MUTATION_OPS)[number];\n\n// `unknown` 有**五**个成因,措辞必须互不相同 —— 它们的下一步动作不一样:\n// · 没应答 → 去查这个模型为什么打不通;\n// · 缺某一项 → 去问服务端为什么少发这一项;\n// · allowed 不是布尔 → 去问服务端为什么发了个 null / \"yes\";\n// · 批次里没这一条 → 请求发出去了、结果没回到手上,重跑一轮即可;\n// · 本轮没查 → 是我们自己没查(定向重爬),补一轮就有。\n// 合并成同一句话,`unknown` 就同时兼职五种含义,顾问无从分流。\nconst NOT_QUERIED = '本轮没有查询该模型的 mutation-capability';\n/**\n * 并发带来的**第五种**成因。与「未应答」故意不同名:那一档是**收到了**一个不能用的响应\n * (有 status / error 可查),这一档是批次结果里压根没有这个模型 ——\n * 前者去查服务端与网关,后者重跑一轮就有。与「本轮没查」也不同名:那一档我们根本没发这个请求。\n *\n * 🔴 **可达性说准**:当前的生产探针对**每一个** path 都有落点,所以真实的批次抖动会落进\n * 「未应答(HTTP -1 / network)」,**不会**表现成「批次里没有这个模型」。\n * ⇒ 这一档是**客户端契约的兜底**:本函数只要求调用方给一个「path → Outcome 的映射」,\n * 不要求它对每个 path 都有键。fail-closed 不猜:结果没回来就是没查到,绝不缺省成「允许」。\n */\nconst BATCH_LOST =\n '已随并发批次发出,但批次结果里没有这个模型 —— 结果丢了,' +\n '既不是服务端拒答、也不是本轮没查,重跑一轮即可';\n// 🔴 下面这三句是**调用方**把形状喂坏了(不是服务端),措辞与**上面那几种**一个都不许同名。\n// 「传入的」与「响应里」是这两组的分水岭:前者去查是谁拼的这份 mutation,后者去查服务端。\nconst badSection = (t: string): string => `传入的 mutation 段不是对象(${t}),无法判读`;\nconst badCell = (op: string, t: string): string => `传入的 mutation 里 ${op} 这一项不是对象(${t}),无法判读`;\nconst missingCell = (op: string): string => `传入的 mutation 里没有 ${op} 这一项`;\n\nexport const WORDING =\n 'writable=false 的两种原因:derived:<kind>(派生字段,服务端算出来的)、' +\n 'readonly(元数据声明只读)。autoGenerate=true 的字段(工号 / 组织 / 岗位 / 法人编码等)' +\n '**不是**不可写:服务端只在字段为空时生成,调用方手工指定的值优先 —— 由独立标记 ' +\n '`autoGeneratedIfAbsent` 点名(不送则生成、送则采纳),不再进 notWritableReason。' +\n '🔴 writable=true **只表示元数据没声明这两种不可写**,不表示这个字段该由顾问填 —— ' +\n '实测 createById / updateById 这 1476 个审计字段四个标志全是 false,照样是 writable=true,' +\n '它们由 ref.auditBackref=true 点名(topology 把同一批边排除出依赖图,这里不排除:' +\n 'mapping 是字段清单,不是依赖图)。' +\n 'ref.valueDomain 只在目标是码表(DYNAMIC / CO_*)时给出;指向业务模型时为 null —— ' +\n // 🔴 这段拆分**必须自己对得上账**:原文写「234 个引用目标里只有 32 个是码表,其余 200 个」\n // —— 32 + 200 = 232 ≠ 234,产物里唯一解释「为什么这个 ref 没有 valueDomain」的那段话\n // 自己少了 2 个目标。判据从措辞里抠数做加法,不背数字。\n '实测 234 个声明出来的引用目标里,230 个在名册中:其中 32 个是码表、198 个是业务模型' +\n '(User 1564 / OrgUnit 292 / Employee 199 …)要按业务键现查;另有 4 个悬空目标' +\n '(声明了引用但不在 /api/system/models 名册里,见 topology.json 的 dangling)。' +\n 'valuedomain 里没有业务模型的对照表,指一个不存在的文件比不指更糟:' +\n '顾问会以为那份对照表只是还没生成。' +\n 'ref.labelFieldSource=unknown 表示产品没声明显示字段名' +\n '(实测进图的 2923 条边里 1366 条没声明),**不是**「默认用 name」—— ' +\n '那 1366 条指向 224 个在册目标,其中 60 个(27%,目标模型口径)连 name 字段都没有,' +\n '按边算 152 条(11%,字段口径)落在它们身上。' +\n 'mutation.allowed 恒为三个值之一:true / false / \"unknown\"。' +\n '\"unknown\" 表示该模型这一项没查到,**不是**「允许」;具体是哪一种没查到看 reason' +\n '(服务端侧:没应答 / 响应缺这一项 / allowed 不是布尔;工具侧:并发批次里没拿到这个模型的结果;' +\n '调用方侧:本轮没查 / 传入的 mutation 段不是对象 / 传入的这一项不是对象 / 传入的里没有这一项 —— ' +\n '八种成因措辞各不相同,「传入的」开头的是拼这份基线的人搞错了,不是服务端)。' +\n 'enumCount 数的是该字段候选值的**元素个数**,与 index/fields.jsonl 里该字段的 enums 行数相等。';\n\n/**\n * mutation 段里的一格。\n *\n * 🔴 `allowed` 是**可选的**:`mutationSection` 会把调用方给的对象**原样透传**\n * (取值归一是 `collectMutation` 的职责,两处都归一就会分叉)——\n * 而一个手拼的 `{reason: \"...\"}` 里压根没有 `allowed` 键。写成必填会逼出一个\n * `as unknown as` 断言,那等于把「这里可能没有这个键」这件事从类型里抹掉,\n * 而 `_index.json` 的补集判据正是为了接住它才写成补集的。\n */\nexport interface MutationCell {\n allowed?: unknown;\n reason?: unknown;\n}\nexport type MutationSection = Record<string, MutationCell>;\nexport type MutationMap = Record<string, MutationSection>;\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\nfunction typeName(v: unknown): string {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'list';\n return typeof v;\n}\n\n/**\n * → `{mutation, problems}`,`mutation[modelKey][op] = {allowed, reason}`。\n *\n * 🔴 **走 `getMany` 并发**,形态与 `collectActionGates` 逐字同款:`path → model` 的映射\n * 把「哪个模型的哪一项没查到」这个归属重新建起来,并**必须**处理「批次里没有返回该 path」\n * 那一档 —— 见 `BATCH_LOST`。\n *\n * 去重后再查:重复的 modelKey 会白打一次请求,并让同一个问题在 `problems` 里记两遍,\n * 下游按条数报「有 2 个问题」就是虚报。\n * 🔴 `paths` 按**排序后**的 key 构建 ⇒ 返回的 `mutation` 仍是排序序:产物可 diff\n * 这件事不能因为换了取数方式就丢。\n */\nexport async function collectMutation(\n probe: ReconProbe,\n modelKeys: string[],\n): Promise<{ mutation: MutationMap; problems: string[] }> {\n const keys = [...new Set(modelKeys)].sort();\n const paths = new Map<string, string>();\n for (const k of keys) paths.set(MUTATION_PATH(k), k);\n const results: Record<string, Outcome> = probe.getMany\n ? await probe.getMany([...paths.keys()])\n : await sequentialGet(probe, [...paths.keys()]);\n\n const mutation: MutationMap = {};\n const problems: string[] = [];\n for (const [path, key] of paths) {\n const out = results[path];\n if (out === undefined) {\n // 批次里没有这个模型:客户端契约的兜底。fail-closed。\n problems.push(`${key}: mutation-capability ${BATCH_LOST}`);\n mutation[key] = Object.fromEntries(\n MUTATION_OPS.map((op) => [op, { allowed: 'unknown', reason: BATCH_LOST }]),\n );\n continue;\n }\n const body = out.error === 'ok' ? out.body : null;\n if (!isRecord(body)) {\n // 🔴 HTTP 200 但载荷不是对象(数组 / 字符串 / null)与 500 同档:我们**不知道**\n // 这个模型能不能写。乐观缺省成 allowed=true 会让脚本直接开写,\n // 然后在客户环境里撞一堵墙 —— 而那时已经写进去半份数据了。\n const reason = `未应答(HTTP ${out.status} / ${out.error})`;\n problems.push(`${key}: mutation-capability ${reason}`);\n mutation[key] = Object.fromEntries(MUTATION_OPS.map((op) => [op, { allowed: 'unknown', reason }]));\n continue;\n }\n const entry: MutationSection = {};\n for (const op of MUTATION_OPS) {\n const cell = body[op];\n // 🔴 判据是「有没有 allowed 这个**键**」,不是「allowed 真不真」。这一支与下面\n // 那道归一都会产出 unknown,但**措辞不同名**、指向的下一步也不同:这里是\n // 「服务端少发了这一项 / 这一项压根不是对象」,下面是「发了,但值不是布尔」。\n if (!isRecord(cell) || !('allowed' in cell)) {\n entry[op] = { allowed: 'unknown', reason: `响应里没有 ${op} 这一项` };\n problems.push(`${key}: mutation-capability 响应缺 ${op}`);\n continue;\n }\n const allowed = cell.allowed;\n // 🔴 `allowed` 归一到**三个值**:true / false / \"unknown\"。非布尔一律归 unknown,\n // 而不是原样带出去。这不是洁癖,是**让下游的判据能自证前提**:`_index.json` 用\n // `=== false` 认「被拒」、用补集认「没查到」,原样带出去的第四种取值会两边都不落 ——\n // 于是它在索引里彻底隐身,只有逐个打开 742 份 sheet 才看得见。\n if (typeof allowed !== 'boolean') {\n entry[op] = { allowed: 'unknown', reason: `allowed 不是布尔(${JSON.stringify(allowed)}),无法判读` };\n problems.push(`${key}: mutation-capability 的 ${op} 项 allowed 不是布尔(${JSON.stringify(allowed)})`);\n continue;\n }\n entry[op] = { allowed, reason: cell.reason ?? null };\n }\n mutation[key] = entry;\n }\n return { mutation, problems };\n}\n\n/**\n * 把**调用方传进来的**某个模型的 mutation 段整成「四个 op、每个都是对象」的形状。\n *\n * 🔴 这道闸原先只装在 `collectMutation` 那一侧,`buildMapping` 这一侧没有。\n * 而不经 `collectMutation` 的调用方是真实存在的:手拼、断点续跑从磁盘读回、\n * 定向重爬合并两轮结果。实测的后果不是「记漏一档」,是从落盘那一步抛出一条\n * 无源码可读的栈 —— 正是本模块别处一直在防的那种。\n *\n * 🔴 形状归形状,**取值不动**:cell 是对象就原样透传,哪怕 `allowed` 是 null / \"yes\"。\n * 取值的归一是 `collectMutation` 的职责,这里再归一一次,两处判读就又分叉了;\n * 而未归一的取值有 `_index.json` 的补集判据兜着(它对任何取值都自证)。\n */\nfunction mutationSection(entry: unknown): MutationSection {\n if (entry === undefined || entry === null) {\n // 「本轮没查这个模型」——与「查了、但形状坏」不是一回事。\n return Object.fromEntries(MUTATION_OPS.map((op) => [op, { allowed: 'unknown', reason: NOT_QUERIED }]));\n }\n if (!isRecord(entry)) {\n const reason = badSection(typeName(entry));\n return Object.fromEntries(MUTATION_OPS.map((op) => [op, { allowed: 'unknown', reason }]));\n }\n const section: MutationSection = {};\n for (const op of MUTATION_OPS) {\n const cell = entry[op];\n if (isRecord(cell)) {\n section[op] = cell;\n } else if (op in entry) {\n section[op] = { allowed: 'unknown', reason: badCell(op, typeName(cell)) };\n } else {\n // 补齐缺的 op:sheet 的 mutation 段恒有四项,下游取 `sheet.mutation[op]` 不会落空。\n section[op] = { allowed: 'unknown', reason: missingCell(op) };\n }\n }\n return section;\n}\n\n/**\n * → `{writable, reason}`。\n *\n * 🔴 判据只用**观测到有区分度**的键。`isTree` / `multiple` / `treeLoadMode`\n * 在实测的 14299 个字段上分别恒为 false / false / \"lazy\" —— 拿它们分支等于写一条\n * 夹具永远走不到的死路。\n *\n * 🔴 顺序即优先级:`derived` 排第一,因为它是**唯一带成因**的那一档\n * (`derived:aggregate` / `derived:expression`,实测 55 个字段,分布 40 / 15)。\n * 两种成因同时成立时先报最有解释力的那个 —— 顾问看到 `readonly` 只会去问\n * 「谁把它设成只读的」,看到 `derived:expression` 才知道这个值是算出来的、根本没有「填」这个动作。\n *\n * 🔴 `autoGenerate` **不是**不可写。产品契约是「**仅在字段为空时生成;调用方手工指定的值优先**」\n * —— 主数据(工号 / 组织编码 / 岗位编码)的导入靠的就是这档语义。把它标成 `writable:false`\n * 会让按元数据编程的调用方不发 `code`,于是业务键被服务端流水号静默替换。\n * 因此这里只给一个**新标记** `autoGeneratedIfAbsent`,不再进 `notWritableReason`。\n */\nfunction writability(field: Record<string, unknown>): { writable: boolean; reason: string | null } {\n const derived = field.derived;\n if (derived) {\n // 形状按实测是对象(`{kind:\"expression\", expression:\"AGE(birthDate)\"}`),\n // 但不许按固定形状解包:只取 kind,别的忽略;不是对象时记 unknown 而不是崩。\n const kind = isRecord(derived) ? derived.kind : null;\n return { writable: false, reason: `derived:${typeof kind === 'string' && kind ? kind : 'unknown'}` };\n }\n if (field.isReadonly) return { writable: false, reason: 'readonly' };\n return { writable: true, reason: null };\n}\n\nexport interface MappingSheet {\n model: string;\n crawled: true;\n wording: string;\n mutation: MutationSection;\n fieldCount: number;\n fieldsProblem: string | null;\n fields: Array<Record<string, unknown>>;\n}\n\n/**\n * → 每模型一份 sheet,按 modelKey 排序。\n *\n * `edges` 是 `refEdges` 的原样输出(七键),本模块**不自己判读** `object` / `refer`:\n * 两处各判一次就会长出「一边算 2929 条、另一边算 2926 条」这种谁也说不清的差额。\n *\n * `codeTables` 是**码表模型 key 的集合**。只有目标落在这个集合里时才给 `ref.valueDomain`\n * —— 指向一个不存在的对照表文件比不指更糟。缺省是空集合:没传就一个都不指,\n * **不会**凭前缀猜(`CO_` 前缀是某个租户的观测,不是产品契约)。\n *\n * 🔴 排序是产物可 diff 的前提:`metas` 的键序**不恒定**(定向重爬与全量爬出来的顺序不同)。\n */\nexport function buildMapping(\n metas: Record<string, unknown>,\n edges: RefEdge[],\n mutation: unknown,\n codeTables: Iterable<string> = [],\n): MappingSheet[] {\n const codeSet = new Set(codeTables);\n const byField = new Map<string, RefEdge>();\n for (const e of edges) byField.set(`${e.fromModel} ${e.fromField}`, e);\n\n const sheets: MappingSheet[] = [];\n for (const modelKey of Object.keys(metas).sort()) {\n // 判读走 `metaFields` 单一口径 —— 与 `extractFieldRows` / `refEdges` 同源。\n // 🔴 **problem 必须接住,不许丢掉**:丢掉之后「载荷是 `{fields: null}` / 根本不是对象」\n // 这种**爬过、但判不出字段**的第三态,产出 `{crawled: true, fieldCount: 0, fields: []}`\n // —— 与「爬过且确实没字段」**逐字相同**。\n // 台账落在**本 sheet 自己**的 `fieldsProblem` 上,与 `extractFieldRows` 的 `dropped`\n // 不是同一个落点:那份台账在**另一份产物**里,顾问打开 `mapping/X.json` 看到 0 个字段时,\n // 不该还得知道去别处翻。计数**不**重复:那边记的是行数,这里记的是一句人话。\n const { fields, problem } = metaFields(metas[modelKey]);\n const rows: Array<Record<string, unknown>> = [];\n for (const f of fields) {\n if (!isRecord(f)) continue;\n const key = f.key;\n // 🔴 key 纪律必须与 `extractFieldRows` 里「没有可用的 key」那一支逐字一致 ——\n // **非字符串的 key 也要丢**。只判真假会让 `key: 7` 活着进 mapping,于是:\n // ① 同一个字段在 index/fields.jsonl 里被丢进 dropped、在 mapping 里却存在,\n // 两个消费者对同一份输入各判各的;\n // ② 下游一排序就类型错 —— 客户现场一条无源码可读的栈。\n if (typeof key !== 'string' || !key) continue;\n const { writable, reason } = writability(f);\n const edge = byField.get(`${modelKey} ${key}`);\n const ref = edge\n ? {\n toModel: edge.toModel,\n carrier: edge.carrier,\n // labelField / labelFieldSource 原样透传:没声明就是 null + \"unknown\",\n // 不许在这里兜底成 \"name\"(实测 60 个目标模型连 name 字段都没有)。\n labelField: edge.labelField,\n labelFieldSource: edge.labelFieldSource,\n valueDomain: codeSet.has(edge.toModel) ? `valuedomain/${edge.toModel}.json` : null,\n // 依据只有**字段名约定**,与 topology 同源、口径也同 —— 不是产品声明:\n // 实测这 1476 个字段的 isReadonly/autoGenerate/isList/isInfo 全为 false。\n auditBackref: AUDIT_SET.has(key),\n }\n : null;\n const enums = f.enums;\n rows.push({\n key,\n // 🔴 中文名的键是 `name`,**不是 `label`**;必填的键是 `isRequired`,**不是 `required`**。\n // 接错不报错也不崩,产物里只是「全是英文键名」或「一个字段都不必填」。\n name: f.name ?? '',\n type: f.type ?? '',\n required: Boolean(f.isRequired),\n writable,\n notWritableReason: reason,\n // 🔴 autoGenerate=true 的字段**可以**由调用方指定 —— 服务端只在字段为空时才生成。\n // 这是 writable=true 之外的一道**独立标记**,避免把编码当成只读而漏发、导致业务键漂移。\n autoGeneratedIfAbsent: Boolean(f.autoGenerate),\n ref,\n // 🔴 数**全部元素**,不是「形状认得出的那些」。`fields` 侧对非对象元素也逐个产出一行\n // (带 `_kitValueSource` 认领),这里若只数对象,一个 `enums: [\"A\",\"B\"]` 的字段\n // 在 mapping 里就是 enumCount=0 —— 与「这个字段没有候选值」同形,\n // 而 index/fields.jsonl 里明明有两行。\n enumCount: Array.isArray(enums) ? enums.length : 0,\n });\n }\n sheets.push({\n model: modelKey,\n // 「爬过且这个模型确实没有字段」必须与「没爬过」(文件不存在)区分开。\n crawled: true,\n wording: WORDING,\n mutation: mutationSection(isRecord(mutation) ? mutation[modelKey] : mutation),\n fieldCount: rows.length,\n // 🔴 `null` = 载荷判读正常(fieldCount=0 就是「这个模型确实没字段」);\n // 非 null = **爬过、但判不出字段**,此时 fieldCount=0 不代表「没字段」。\n fieldsProblem: problem ?? null,\n fields: rows,\n });\n }\n return sheets;\n}\n\n/**\n * 取某份 sheet 里某个 op 的 `allowed`;形状不对时返回 `'unknown'` 而**不是**抛。\n *\n * 🔴 `buildMapping` 出来的 sheet 形状已由 `mutationSection` 保住,所以这一支\n * 只对**手拼 sheets 直调 `writeMapping`** 的调用方生效。留着它,是为了让\n * 「划分对任何调用方都自证」这句话对**形状**也成立、而不只是对取值域成立 ——\n * 一份坏形状的 sheet 该让它落进 unknown 被点名,不该让整份索引以一条栈收场。\n */\nfunction allowedIn(sheet: Record<string, unknown>, op: string): unknown {\n const section = sheet.mutation;\n const cell = isRecord(section) ? section[op] : undefined;\n return isRecord(cell) ? cell.allowed : 'unknown';\n}\n\nconst PATH_SEPARATORS = ['/', String.fromCharCode(92)];\n\n/**\n * modelKey 能不能安全地当文件名用 → 不能则给出人话理由。\n *\n * 🔴 **落盘闸守的是产物根,不是 `mapping/` 这一层**,所以它接不住这一档:\n * `mapping/../evil.json` 解析后仍在根里,闸不响,文件却落在 `models.json` 旁边,\n * 既按 modelKey grep 不回去、又与别的产物混在一起。\n * ⇒ 本模块自己判「这个键能不能当一段文件名」,落盘闸留作最后一道。\n *\n * 拒绝而不是消毒:消毒后文件名不再等于 modelKey,顾问 `ls mapping/ | grep Employee` 就对不上了。\n */\nexport function unsafeKeyReason(modelKey: unknown): string | null {\n // ⚠️ 这一支**守不到主路径**:走正常管道时 `buildMapping` 里的排序会先对非字符串 key 出问题。\n // 它是给**直调 `writeMapping` 的调用方**(手拼 sheets、从磁盘读回 sheet 再落盘)留的。\n if (typeof modelKey !== 'string' || !modelKey) return `modelKey 不是非空字符串(${JSON.stringify(modelKey)})`;\n if (PATH_SEPARATORS.some((s) => modelKey.includes(s))) {\n return `modelKey 含路径分隔符,不能当文件名:${JSON.stringify(modelKey)}`;\n }\n if (modelKey === '.' || modelKey === '..') return `modelKey 指向目录本身或上级:${JSON.stringify(modelKey)}`;\n // 🔴 与 valuedomain 的落盘判据**对齐**。此前两侧不一致,实测两个后果,都是「产物在说假话」:\n // · 前导 `.`:`mapping/.hidden.json` **写得出来** —— `ls mapping/` 看不见它,\n // 它却同时计进 modelCount **和** sheetsWritten,于是 `sheetsNotWritten === 0` 在声称\n // 「一份都没漏」。而同一个 key 在 valuedomain 那边是被拒的。\n // · 前导 `_`:`_index` 是本目录**工具自己的保留文件名**。一张叫 `_index` 的表会把\n // `mapping/_index.json` 顶掉 —— 顶掉的正是这份索引本身,而索引里的 sheetsWritten\n // 还在自称一切正常。\n if (modelKey.startsWith('.') || modelKey.startsWith('_')) {\n return (\n `modelKey 以 . 或 _ 开头,不能当文件名:${JSON.stringify(modelKey)} —— ` +\n '前者产出 ls 看不见的隐藏文件、后者会顶掉工具自己的 _index.json'\n );\n }\n return null;\n}\n\nfunction reasonTally(sheets: MappingSheet[]): Record<string, number> {\n const tally = new Map<string, number>();\n for (const sheet of sheets) {\n for (const f of sheet.fields) {\n const r = f.notWritableReason;\n if (typeof r === 'string' && r) tally.set(r, (tally.get(r) ?? 0) + 1);\n }\n }\n return Object.fromEntries([...tally.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));\n}\n\nexport interface WriteMappingOptions {\n problems?: string[] | null;\n danglingValueDomain?: unknown[] | null;\n carryOver?: unknown;\n roundId?: string | null;\n}\n\n/**\n * 一模型一文件 + 一份索引。返回写出的路径,顺序与 `sheets` 一致、`_index.json` 在最后。\n *\n * 一模型一文件是为了让顾问能 `cat mapping/Employee.json` 直接看 —— 742 个模型挤进一个\n * 文件时,那份文件在客户现场既 grep 不动也读不完。\n *\n * 🔴 **一个写不下来的 modelKey 不许掀掉整份产物**:抛出去的话,前面几百个模型写了、\n * 后面的没写,而磁盘上看不出是在哪一步断的。转成 `_index.json` 里的 `unsafeModelKeys`\n * 显式条目 —— 「没写下来」与「这个模型不存在」在产物里必须长得不一样。\n *\n * 🔴 后四项都是**台账,不是装饰**。它们此前只进 CLI 摘要,而摘要跑完就没了 ——\n * **产物是被单独拷走的那一半**。一律 **`null` = 调用方没告诉我**、\n * **`[]` / 具体值 = 对过账**。键**恒发**,缺席不许兼职「没有」。\n */\nexport function writeMapping(\n layout: ReconLayout,\n sheets: MappingSheet[],\n opts: WriteMappingOptions = {},\n): string[] {\n const written: string[] = [];\n const unsafe: Array<Record<string, unknown>> = [];\n for (const sheet of sheets) {\n const modelKey: unknown = sheet.model;\n let reason = unsafeKeyReason(modelKey);\n if (reason === null) {\n try {\n written.push(layout.writeJson(`mapping/${modelKey as string}.json`, sheet));\n continue;\n } catch (err) {\n // 落盘的越界闸(产物根那一层)—— 上面的判据没想到的形态由它兜底。\n //\n // ⚠️ **只接越界这一档,别的照旧往上抛**:磁盘满 / 只读挂载这类是**系统性**故障,\n // 逐条吞掉只会产出一份处处是窟窿的基线。\n // 🔴 但这条理由有个**例外,如实记在这里**:超长 modelKey(文件名 > 255 字节)\n // 触发的 ENAMETOOLONG 是**逐 key 的**、不是系统性的,它仍会掀掉整批。\n // 没有改,因为 742 个真实 modelKey 全都很短;真撞上了,修法是在\n // `unsafeKeyReason` 里加一条长度判据。\n if (!(err instanceof LayoutContainmentError)) throw err;\n reason = (err as Error).message;\n }\n }\n unsafe.push({\n model: typeof modelKey === 'string' ? modelKey : JSON.stringify(modelKey),\n reason,\n fieldCount: sheet.fieldCount,\n });\n }\n written.push(\n layout.writeJson('mapping/_index.json', {\n crawled: true,\n wording: WORDING,\n // 🔴 轮次身份。一轮 recon 被打断时,**这一份最容易留在上一轮**(它是编排里最后写的\n // 几份之一)—— 而那时 `mapping/` 里缺某个模型,与「那个模型的 modelKey 不安全、\n // 被拒写」同形。`null` = 调用方没告诉我,恒发键。\n roundId: opts.roundId ?? null,\n // 🔴 三份台账**恒发键**(null = 调用方没告诉我,[] = 对过账且一条都没有)。\n // 明细与摘要两处都要有:只进摘要 ⇒ 产物被单独拷走后就没了;只进产物 ⇒\n // 跑完那一刻看不见。少任何一处都等于没记。\n problems: opts.problems ?? null,\n danglingValueDomainPointers: opts.danglingValueDomain ?? null,\n [KIT_CARRY_OVER_KEY]: opts.carryOver ?? null,\n modelCount: sheets.length,\n // 🔴 「有几个模型」与「有几份 sheet 文件」不是同一个数,差额就是下面这一档 ——\n // 不点名的话,`ls mapping/ | wc -l` 与 modelCount 对不上而没人说得清为什么。\n sheetsWritten: written.length,\n unsafeModelKeys: unsafe,\n notWritableByReason: reasonTally(sheets),\n // 🔴 判据是 `=== false`,与下面 `mutationUnknown` 的补集判据**配成一个划分**。\n // 它的牙来自那条**没经过归一的裸 null** 夹具:`!allowed` 对 null 为真,\n // 于是同一个模型会同时落进 denied 和 unknown,划分不变量当场把它逮住。\n mutationDenied: Object.fromEntries(\n MUTATION_OPS.map((op) => [\n op,\n sheets\n .filter((s) => allowedIn(s as unknown as Record<string, unknown>, op) === false)\n .map((s) => s.model)\n .sort(),\n ]),\n ),\n // 与 mutationDenied 同一批数据的另一面:没查到的那些必须也点名,\n // 否则「不在 denied 里」会被读成「允许」。\n //\n // 🔴 判据是**补集**(既不是 true 也不是 false),不是 `=== 'unknown'`。\n // 差别只在一种输入上,而那种输入是真实可达的:`collectMutation` 归一过的 mutation 里\n // 只有三值,两种写法等价;但 `writeMapping` **不检查这个前提**,而喂给它的 mutation\n // 完全可能没经过归一 —— 手拼、断点续跑从磁盘读回、定向重爬合并两轮结果,都是这条路。\n // 那时一个 `{allowed: null}` 在 `=== 'unknown'` 的写法下两个清单都不进,**在索引里隐身**。\n // 写成补集,划分在**取值域**上对任何调用方都自证。\n // 🔴 措辞收窄过一次:补集只管**取值**,管不了 **cell 的形状** —— 未归一的调用方喂\n // `{create: \"yes\"}`(cell 不是对象)时原先直接抛。形状那一半由 `mutationSection`\n // (build 侧)与 `allowedIn`(本函数侧)两道闸补上,两半齐了「对任何调用方都自证」才是句实话。\n mutationUnknown: Object.fromEntries(\n MUTATION_OPS.map((op) => [\n op,\n sheets\n .filter((s) => {\n const a = allowedIn(s as unknown as Record<string, unknown>, op);\n return a !== true && a !== false;\n })\n .map((s) => s.model)\n .sort(),\n ]),\n ),\n }),\n );\n return written;\n}\n","/**\n * `index/models.md`:给人读的一页式模型摘要(一模型一节,契约清单 §3.4,17 条)。\n *\n * 它不是 `fields.jsonl` 的替代品——检索走那份 JSONL,这份负责「顾问翻一眼就知道\n * 这个环境有什么、哪些动作是声明只读的、哪些格子根本没爬到、哪些东西没能进索引」。\n *\n * 🔴 整节只有一条主线:**「不知道」必须渲染成「(未声明)/ 未爬取 / 无法判读」,\n * 永远不许出现 `null`/`undefined`,更不许把「没爬到」渲染成「字段数:0」**。\n * 这是整份产物给顾问看的那一面,把未知渲染成 0 会直接把人导向错误结论。\n */\nimport { modelEntries } from '../fingerprint';\n// 🔴 判读 meta 载荷走 `metaFields` 这**一个**口径:本模块与 fields 吃的是同一份 metas\n// (runner 一次爬取喂给两边)。各写各的判读 = 一边记台账继续跑、另一边裸抛,\n// 而这种半容忍只在客户现场那一次暴露。\nimport { metaFields } from './fields';\n\n/**\n * 🔴 口径写死在产物里:闸的覆盖面永远是 state-scoped。\n * 产品没有「列出可用 state」的端点,「全集」不是一个可兑现的词——\n * 说成 full 就是在客户现场发一份自己都兑现不了的承诺。\n */\nconst SCOPE_NOTE =\n '闸的覆盖面是 **state-scoped**:产品里 state 不可枚举,' +\n '本文件只反映本次爬取所用的那一个 state。页签类问题必先问 state,不要信 base meta。';\n\n/** 无闸模型必须有这一句。省掉它,「没有只读闸」与「这一节没渲染出来」在产物里同形。 */\nconst NO_GATE =\n '- 声明只读的动作:**未发现声明只读的动作**(未声明 = 按写动作对待,无论它叫 preview 还是 check)';\n\n/**\n * 「meta 没爬到」与「这个模型没有字段」是两个值:实测 761 个模型里 19 个 meta 恒 404,\n * 渲染成「字段数:0」会让读的人得出一个错误结论。\n */\nconst FIELDS_NOT_CRAWLED = '- 字段数:未爬取(该模型 meta 不可用,详见 unavailable.json)';\n\n/** 缺值兜底。产物里出现 `null` 会被当成真的取值去搜——缺声明就明说缺声明。 */\nconst UNDECLARED = '(未声明)';\n\nconst ORPHAN_TITLE = '## 未归属的只读闸';\n\nexport interface GateRow {\n model?: string;\n key?: string;\n /**\n * 🔴 取值域含 **`null`**,不是只有 `undefined`:`gates.ts` 对缺失的 label\n * **显式兜成 `null`** —— 留 `undefined` 会被 `JSON.stringify` 把整个键删掉,\n * 闸条目就从 9 键变 8 键。两边的取值域必须对得上,否则这两份移植互相矛盾,\n * 而矛盾只会在编排层把 `Gate[]` 喂进来的那一刻才暴露(实测正是如此)。\n * 渲染侧 `gate.label || ''` 两态同兜,行为上没有第二档。\n */\n label?: string | null;\n source?: string[];\n guard?: string;\n}\n\nfunction gateDesc(gate: GateRow): string {\n // action 级中文名是 `label`(meta 的**字段**级叫 `name`,两者不同源,别统一)。\n // 它是顾问唯一能按**中文动作名** grep 到只读动作的落点;\n // 缺失时省掉这一截,而不是把 `null` 渲染进产物冒充一个动作名。\n const label = gate.label || '';\n const named = `\\`${gate.key}\\`` + (label ? ` · ${label}` : '');\n const source = (gate.source ?? []).join('+') || UNDECLARED;\n // guard 的前提由 gates 那一层保住(恒有值),但**别的喂法保不住**——缺就明说缺。\n const guard = gate.guard || UNDECLARED;\n return `${named}(source=${source},guard=${guard})`;\n}\n\n/**\n * `dropped` 是 `extractFieldRows` 的第二个返回值。\n *\n * 它必须在这里有**看得见的落点**:只被 runner 记进摘要还不够——产物是会被单独拷走的文件,\n * 「有 N 个字段没能进索引」这件事不写在纸面上,就与「产品里根本没有这些字段」同形。\n */\nexport function renderModelsMd(\n modelsBody: Record<string, unknown>,\n metas: Record<string, unknown>,\n gates: GateRow[],\n dropped: readonly string[] = [],\n): string {\n const byModel = new Map<string | undefined, GateRow[]>();\n for (const g of gates) {\n const list = byModel.get(g.model) ?? [];\n list.push(g);\n byModel.set(g.model, list);\n }\n\n const parts: string[] = [\n '# 模型索引',\n '',\n `共 ${modelsBody.totalCount ?? 0} 个模型;类型分布 ${JSON.stringify(modelsBody.typeCounts ?? {})}。`,\n '',\n SCOPE_NOTE,\n '',\n '字段一行一条在 `index/fields.jsonl`(按中文名 / 同义词 grep 即可反查 model + key)。',\n '',\n ];\n\n if (dropped.length > 0) {\n parts.push(\n `> ⚠️ 有 **${dropped.length}** 个字段没能进 \\`index/fields.jsonl\\`,` +\n '逐条如下(它们在索引里与「产品没有这些字段」同形,所以必须在这里点名):',\n );\n for (const d of dropped) parts.push(`> - ${d}`);\n parts.push('');\n }\n\n const listed = new Set<string>();\n // 🔴 `models[]` 里混进一个非对象,这里就会当场抛,且发生在**已经爬完、\n // 正要落最后一份产物**的时候——前面所有网络工作全部白费。\n // 走同一道门 `modelEntries()`:它只放对象型条目过。\n for (const m of modelEntries(modelsBody)) {\n const key = String(m.modelKey ?? '');\n listed.add(key);\n // 🔴 取名字必须容缺:实测 761 个 modelKey 里只有 739 个带 name。\n // 缺名字时不缀分隔符,也绝不渲染 `null`——产物里的 null 会被当成真的模型名去搜。\n const name = m.name || '';\n parts.push(name ? `## ${key} · ${name}` : `## ${key}`);\n parts.push('');\n parts.push(`- 类型:${m.type || UNDECLARED}`);\n if (!Object.prototype.hasOwnProperty.call(metas, key)) {\n parts.push(FIELDS_NOT_CRAWLED);\n } else {\n const { fields, problem } = metaFields(metas[key]);\n // 三个值不许糊成一个:没爬到 / 爬到了但载荷判读不了 / 爬到了确实是 N 个字段。\n parts.push(\n problem === undefined\n ? `- 字段数:${fields.length}`\n : `- 字段数:无法判读(${problem}),该模型没有字段进索引`,\n );\n }\n const gs = byModel.get(key) ?? [];\n if (gs.length > 0) {\n parts.push('- 声明只读的动作:');\n for (const g of gs) parts.push(` - ${gateDesc(g)}`);\n } else {\n parts.push(NO_GATE);\n }\n parts.push('');\n }\n\n // 🔴 模型清单里没有的闸**不许蒸发**。触发条件是真的:清单响应形状异常时会回落成空清单,\n // 那一刻**全部**闸都会变成孤儿——而 models.md 是顾问唯一能按中文动作名 grep 到只读闸的落点,\n // 丢一条不留痕就与「这条闸不存在」同形。\n const orphans = gates.filter((g) => !listed.has(String(g.model ?? '')));\n if (orphans.length > 0) {\n parts.push(ORPHAN_TITLE);\n parts.push('');\n parts.push(\n `以下 **${orphans.length}** 条闸的模型不在本轮的模型清单内 —— ` +\n '**为什么不在,本模块没有查过**。🔴 **别读成「发现了隐藏模型」**。' +\n '已知**至少**两条路长成这样(不是全集):① 模型清单本身没取全' +\n '(清单响应形状异常时会回落成空清单);② 这几条闸是**沿用上一轮产物**的,' +\n '而那一轮的名册与本轮不同(见 `gates.json` 的 `coverage._kitCarryOver`)。' +\n '先看 `models.json` 的 `_kitPremiseCheck`。',\n );\n parts.push('');\n // 缺 model 的 gate 走「(未声明)」兜底:产物里的 null 会被当成真的模型名去搜——\n // 与本文件「绝不渲染 null」是同一条纪律。\n for (const g of orphans) parts.push(`- \\`${g.model || UNDECLARED}\\` · ${gateDesc(g)}`);\n parts.push('');\n }\n\n return parts.join('\\n');\n}\n\nexport function writeModelsMd(\n layout: { writeText(rel: string, text: string): string },\n text: string,\n): string {\n return layout.writeText('index/models.md', text);\n}\n","/**\n * `ref.valueDomain` 悬空指针的**第二条独立证据链**。\n *\n * 🔴 存在理由是 `mapping.ts` 自己的 WORDING:「指一个不存在的文件比不指更糟:顾问会\n * 以为那份对照表只是还没生成」——那句话把悬空判成了不可接受的失败,却没有任何检测。\n * 实测得出的三个悬空来源:\n * ① **两侧路径判据不对称**:`valuedomain` 拒 `/` `\\` **与前导 `.`**,\n * `mapping.unsafeKeyReason` 不拒前导 `.` ⇒ `.hidden` 那张表的 sheet 写得出来,\n * 它指向的对照表却被拒了;\n * ② `valuedomain` 落盘撞上 layout 的越界闸(只进 writeProblems,文件没写);\n * ③ **口径差**:「名册里是 DYNAMIC」与「对照表真的写出来了」是两个集合。\n * ③ 已在编排里根治(`codeTableFiles` 取自 `writeValuedomain` 真写出来的那些),\n * 本模块是**第二条独立证据链**:不吃那个推导,直接看盘。两条链互不依赖,\n * 才抓得住「推导本身错了」这一档 —— 只有一条时,推导错了与产物没问题同形。\n *\n * ⚠️ 判据是文件在不在,**不是**「这张码表爬成功了没有」:爬失败的表照样落一份\n * `completeness=unavailable` 的文件,指向它**有效**。\n *\n * 🔴 **射程只到 `buildMapping` 刚产出的 sheets**:本函数直接取 `sheet.fields` /\n * `field.key`,对**手拼的** sheet 不设防 —— 与 `mapping.ts` 给手拼调用方加的三道闸\n * (`mutationSection` / `allowedIn` / `unsafeKeyReason`)口径**故意不一致**。\n * 理由是射程不同:那三道闸守的是 `writeMapping` 这个真有手拼调用方的公开 API\n * (断点续跑读回、合并两轮结果),而这里只吃同一次调用里 `buildMapping` 刚产出的 sheets,\n * 形状由它保证。哪天它被搬去公开 API,形状闸要一起补上,不能只搬函数体。\n *\n * 🔴 **为什么单独一个模块**(Python 侧它是 `runner.py` 的模块私有函数):\n * `runner` 必须有一条守卫钉住「这次对账**真的被调用了**」——Python 用\n * `monkeypatch.setattr(runner_mod, ...)` 换掉模块属性,ESM 里没有这回事。\n * 拆成模块之后 `vi.mock` 换得掉,那条接线守卫才做得出来。\n * ⚠️ 拆分**不等于**公开:它不进 `packages/core/src/index.ts` 的导出面,\n * 上面那条射程纪律照旧成立。\n */\nimport { existsSync } from 'node:fs';\nimport * as path from 'node:path';\n\n/** 本函数吃的 sheet 形状——`buildMapping` 的产物是它的超集。 */\nexport interface AuditSheet {\n model: string;\n fields: Array<Record<string, unknown>>;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\n\n/** 扫每一份 sheet 里非空的 `ref.valueDomain`,逐个确认它指的文件**真的在磁盘上**。 */\nexport function auditValueDomainPointers(\n layout: { dir: string },\n sheets: readonly AuditSheet[],\n): string[] {\n const problems: string[] = [];\n // 同一个指针会在几百份 sheet 里重复出现(`createById → User` 那种量级),只看一次盘。\n const seen = new Map<string, boolean>();\n for (const sheet of sheets) {\n for (const field of sheet.fields) {\n const ref = field.ref;\n const pointer = isRecord(ref) ? ref.valueDomain : null;\n // 空串与 null 一样跳过:`buildMapping` 的取值域是「路径 | null」,\n // 空串两边都不是,报它等于对一个产不出来的形态发警报。\n if (typeof pointer !== 'string' || pointer === '') continue;\n if (!seen.has(pointer)) {\n seen.set(pointer, existsSync(path.join(layout.dir, pointer)));\n }\n if (!seen.get(pointer)) {\n problems.push(\n `${sheet.model}.${String(field.key)}: ref.valueDomain 指向 ${pointer},` +\n '但这份对照表没有落盘(悬空指针:顾问会以为它只是还没生成)',\n );\n }\n }\n }\n return problems;\n}\n","/**\n * 引用边集:整个侦察层里「哪个字段指向哪个模型」的**唯一**产生点(契约清单 §3.3,17 条)。\n *\n * 🔴 载体有两个,不是一个(2026-08-16 实测):\n * · `object`:2926 个字段,主载体(2924 个在 `type=uuid` 上,2 个在 `type=string` 上);\n * · `refer` :3 个字段,与 `object` **从不共现**。\n * 只读 `object` 会少掉 3 条边——这个量级在总数上看不出异常,\n * 正是「枚举载体,不是审改动」那条纪律要防的形态。加载体请改 `REF_CARRIERS`。\n *\n * 🔴 **不得**用 `isTree` / `multiple` / `treeLoadMode` 做任何判断:实测这三个键在全部\n * 14299 个字段上分别恒为 `false` / `false` / `\"lazy\"`,是序列化缺省而非信号。\n * 拿它们分支,另一侧夹具根本走不到,守卫看着有、实际是空的。\n *\n * topology 与 mapping 两份产物都从这里取边。收口在这一处,是为了让它们永远吃同一套判读——\n * 判读一旦分叉,差额(比如一边读了 `refer` 一边没读)只表现为两个数字不等,\n * 从产物里看不出是什么造成的。\n */\nimport { metaFields } from './fields';\n\n/**\n * 🔴 **顺序即优先级**:同一字段同时带两个载体时取靠前的,并且**只产出一条边**——\n * 产出两条会让 topology 的边数与字段数对不上,而这个差额从产物里看不出是共现造成的。\n * 实测中共现从未发生,但这是**代码不变量**,由两条用例钉住。\n */\nexport const REF_CARRIERS = ['object', 'refer'] as const;\nexport type RefCarrier = (typeof REF_CARRIERS)[number];\n\nexport interface RefEdge {\n fromModel: string;\n fromField: string;\n fieldName: string;\n toModel: string;\n carrier: RefCarrier;\n labelField: string | null;\n labelFieldSource: 'declared' | 'unknown';\n}\n\nexport interface DanglingRef {\n fromModel: string;\n fromField: string;\n /** `malformed-target` 那一档这里是**合成的字符串不是模型名**,按 `reason` 分流。 */\n toModel: string;\n carrier: RefCarrier;\n reason: 'not-in-registry' | 'malformed-target';\n}\n\nexport interface RefResult {\n edges: RefEdge[];\n dangling: DanglingRef[];\n}\n\n/**\n * → `{edges, dangling}`。\n *\n * · `edges`:目标在名册里的引用边,按 `(fromModel, fromField)` 排序;\n * · `dangling`:**声明了引用但用不了**的字段,两档靠 `reason` 区分:\n * - `not-in-registry`:目标不在名册里(实测 4 个)。它们不是「没爬到」,\n * 是产品自己声明了一个不存在的目标——顾问照着填会拿到 400。\n * 放进 edges 会让 topology 凭空多出几个不存在的节点,所以分开交出来。\n * - `malformed-target`:载体存在但值不是字符串。🔴 **不能并进「压根没声明引用」里静默吞掉**:\n * 那样「产品声明了一个坏形态的引用」与「这就是个普通字段」在产物里同形,\n * 顾问会把它当普通字段填,运行期才炸。\n *\n * 🔴 **排序是产物可 diff 的前提**:`metas` 的插入序**不是**恒定的\n * (定向重爬与全量爬出来的顺序不同)。不排序,两次跑同一个租户就会 diff 出满屏噪声,\n * 真正的元数据变更淹在里面。\n */\nexport function refEdges(metas: Record<string, unknown>, knownModels: Iterable<string>): RefResult {\n const known = new Set(knownModels);\n const edges: RefEdge[] = [];\n const dangling: DanglingRef[] = [];\n for (const modelKey of Object.keys(metas).sort()) {\n // 判读走 `metaFields` 单一口径——与 `extractFieldRows` 同源。\n // 这里**不再记一份 problem 台账**:同一份 metas 的坏载荷已由 `extractFieldRows`\n // 记进 dropped。这里再记一份,同一个坏载荷就有了两个来源,接线时极易被加两次。\n const { fields } = metaFields(metas[modelKey]);\n for (const field of fields) {\n if (!field || typeof field !== 'object' || Array.isArray(field)) continue;\n const rec = field as Record<string, unknown>;\n const fieldKey = rec.key;\n // 🔴 key 纪律必须与 `extractFieldRows` 里「没有可用的 key」那一支**逐字一致**——\n // 非字符串的 key 也要丢。只判真假会让 `key: 7` 这种字段活着进 edges,于是:\n // ① 同一个字段在 `index/fields.jsonl` 里不存在、在引用边里却存在,两份产物对不上账;\n // ② 排序时字符串与数字比大小当场抛——客户现场一条无源码可读的栈。\n // 复用了 `metaFields` 却不复用 key 纪律,等于只收口了一半。\n if (typeof fieldKey !== 'string' || !fieldKey) continue;\n // 按 `REF_CARRIERS` 循环,一处声明一处读。\n // 🔴 写成循环,是让「元组增了而实现没跟上」这个失败态**根本无从发生**的手段;\n // 守卫兜的是万一有人改回硬编码之后、下一个加载体的人。\n const carrier = REF_CARRIERS.find((c) => rec[c]);\n if (carrier === undefined) continue;\n const target = rec[carrier];\n // 🔴 只判类型,不判空——空值在上面那句 `find` 里就因假值没被选中,\n // 这里再判空是不可达死码(读的人会以为空串能走到这条分支)。\n //\n // 🔴 **这道门只关了一半,别以为关全了**:`find` 的分界线是**真假值**,不是「键在不在」。\n // 于是坏形态分成两拨:非空且非字符串(`{a:1}` / `[\"T\"]` / `5`)走到这里记 dangling ✅;\n // **假值的坏形态(`{}` / `[]` / `0` / `false`)压根没被选中,仍然无痕消失** ❌。\n // 为什么不顺手补上——已实测不是猜:现网 `object`/`refer` 的「键在但值为假」命中数是 **0**\n // (该序列化器对这几个键是**选择性发键**的),切成「键存在」判据在本租户等价。\n // 🔴 那为什么还是不切?**理由不是这个缺口不存在**,而是那张表只是一次观测、不是契约:\n // 别的环境若对 `object` 改成键恒发,切判据会让**每一个字段**都变成 malformed-target,\n // 爆炸半径是整份产物,而我们从 HTTP 面**证不了它不会**。\n // ⇒ 宁可留一个命中数为 0 的缺口,也不换一个爆炸半径是整份产物的判据。\n if (typeof target !== 'string') {\n dangling.push({\n fromModel: modelKey,\n fromField: fieldKey,\n toModel: reprOf(target),\n carrier,\n reason: 'malformed-target',\n });\n continue;\n }\n if (!known.has(target)) {\n dangling.push({ fromModel: modelKey, fromField: fieldKey, toModel: target, carrier, reason: 'not-in-registry' });\n continue;\n }\n const label = rec.objectLabel;\n const declared = typeof label === 'string' && label.length > 0;\n edges.push({\n fromModel: modelKey,\n fromField: fieldKey,\n // 🔴 字段中文名,是「grep 部门 → 落到 {model, key}」这条链上唯一的人话。\n // 接错成 key 会让产物里全是英文键名——语法完好、不报错,\n // 所以键集与取值都由「边的键集逐键钉死」那条用例守着。\n fieldName: typeof rec.name === 'string' ? rec.name : '',\n toModel: target,\n carrier,\n // 🔴 没声明就是 null + \"unknown\",**不许兜底成 \"name\"**:\n // 「不知道」必须与「猜出来的名字」可分。\n // 实测:2926 个 object 字段里只有 1557 个带 objectLabel;\n // 剩下的指向的目标里 60 个 meta 拿得到但**没有 name 字段**。\n // 落空时**不报错**——产物里会是一个语法完好、指向不存在列的显示字段名。\n labelField: declared ? (label as string) : null,\n labelFieldSource: declared ? 'declared' : 'unknown',\n });\n }\n }\n const byFrom = (a: { fromModel: string; fromField: string }, b: { fromModel: string; fromField: string }): number =>\n a.fromModel.localeCompare(b.fromModel) || a.fromField.localeCompare(b.fromField);\n edges.sort(byFrom);\n dangling.sort(byFrom);\n return { edges, dangling };\n}\n\n/** 坏目标的可追查表示。保留原值的形状线索,但它**不是模型名**。 */\nfunction reprOf(v: unknown): string {\n if (typeof v === 'string') return JSON.stringify(v);\n if (v === undefined) return 'undefined';\n try {\n return JSON.stringify(v) ?? String(v);\n } catch {\n return String(v);\n }\n}\n","/**\n * 码表与枚举的值域对照(契约清单 §3.8,44 条)。\n *\n * **存在理由(实测锚定)**:引用字段存的是目标行的 `id`(UUID),不是 `code` ——\n * `Employee.maritalStatusId = 019f6893-…` 对应 `CO_0001` 里 `code=20 / name=已婚` 那一行。\n * 客户 Excel 里写的是「已婚」,没有这份对照表就写不进去。\n *\n * 🔴 completeness 是四档不是三档,而且**「取满了」不等于「全集」**:查询引擎先注入 RBAC\n * 过滤再分页与 count,`totalElements` 本身就是**裁剪后**的可见总数。所以取满只能到\n * `visible-complete`;`authoritative-complete` 需要产品侧的权威值域出口 —— 那个出口\n * 现在不存在,本模块**没有任何分支**会产出它(有一条守卫扫 `collectCodeTables` 的源码\n * 钉住这一点,所以那个函数体里连注释都不许出现这个字符串)。\n *\n * 🔴 规模是量过的,不是估的:72 张 DYNAMIC 表合计 9417 行,最大一张 3212 行,\n * 空表 0 个,`size:0` 探针 72/72 有应答。⇒ 全量快照可行,**不设抽样**。\n *\n * 🔴 **上限只是「每表」的护栏,不是抽样**:本租户一次都不触发,它挡的是\n * 「换个客户,某张码表几十万行」那一档 —— 一张表就能吃掉整轮。\n * 被截断的表按「no silent caps」**两处留痕**:表自己的产物文件\n * (`completeness=partial` + `truncatedBy`)与 `problems`,缺一处等于没记。\n */\nimport type { Outcome, ReconProbe } from '../probe';\nimport type { ReconLayout } from '../layout';\nimport { LayoutContainmentError } from '../layout';\nimport { metaFields } from './fields';\n// 🔴 与编排层 / mapping 同一个键,不抄字面量。\nimport { KIT_CARRY_OVER_KEY } from './carry-over';\n\nexport const COMPLETENESS = ['authoritative-complete', 'visible-complete', 'partial', 'unavailable'] as const;\n/**\n * 实测 `size:5000` 未被截断,本仓未观测到 size 上限。500 是为了让单次响应体\n * 不至于大到在客户 VPN 上超时,不是协议约束。\n */\nexport const PAGE_SIZE = 500;\n/**\n * 🔴 判码表用 `type`,不用 `CO_` 前缀:实测两个集合双向差集为空,但前缀是命名巧合,\n * type 才是产品的声明。前缀一旦被某个租户用在别处,前缀判法会静默多爬/少爬。\n */\nexport const DYNAMIC_TYPE = 'DYNAMIC';\n\n/**\n * 每表上限(顾问可改)。\n *\n * 🔴 **两个默认值都是按实测定的,每个数都能回源**(本机 / 租户 13 实跑,走生产同一条码路):\n * · 最大的一张码表 3212 行 ⇒ 计数探针 1 次 + 7 页 = **8 次请求 / 1.652s**;\n * · 72 张表合计 **156 次请求 / 6.65s**;整轮 recon 全量墙钟 15.16s。\n * ⇒ 请求上限取 **40**(实测最大值的 5 倍,≈ 20000 行/表),墙钟上限取 **30s**\n * (实测最大值的 18 倍)。两个都留了一个数量级的余量,本租户**一次都不触发**。\n *\n * ⚠️ **客户 VPN 的 RTT 至今没有实测**。本机单次往返中位 3.2ms;若按「假设的 200ms」外推,\n * 8 次请求 ≈ 1.6s 的往返,仍远在 30s 之内 —— 但这一句是**外推不是实测**,\n * 别把它转述成量过的。真到了 RTT 高的现场,先撞上的会是请求上限而不是墙钟。\n *\n * 🔴 上限是**每表**的,超限只停这一张、继续下一张 —— 不是整轮失败。\n */\nexport const MAX_REQUESTS_PER_TABLE = 40;\nexport const MAX_SECONDS_PER_TABLE = 30.0;\n\nexport const WORDING =\n 'rows = 本轮以当前登录身份读到的码表行;每行原样保留服务端返回的全部键。' +\n '引用字段存的是这里的 `id`,不是 `code`。' +\n 'completeness: visible-complete = **当前角色可见范围内**取满(不是全集,RBAC 裁剪先于 count);' +\n 'partial = 没取满或有页失败**或触发了工具的每表上限**(后者看这张表的 `truncatedBy`:' +\n 'null = 没触发,对象 = 停在第几页 / 已收多少行 / 触发的是哪个上限 —— 那是**工具**的截断,' +\n '不是服务端的毛病,改上限重跑就能取全);' +\n 'unavailable = 连计数都没读到(crawled=false,与「这张码表是空的」是两回事);' +\n 'authoritative-complete = 需要产品侧权威值域出口,**本版本永远不会产出**。' +\n 'partial / unavailable 时禁止据此生成写脚本。';\n\nfunction listPath(modelKey: string): string {\n return `/api/models/${modelKey}/list/dsl`;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\n\ninterface ContentRead {\n rows: Array<Record<string, unknown>> | null;\n total: number | null;\n nonDict: number;\n}\n\n/**\n * 判读一份 list/dsl 响应;形状不对时 `rows`/`total` 都是 null。\n *\n * 实测的封套是**顶层无 `data` 包装**:\n * `content, page, size, totalElements, totalPages, first, last, numberOfElements, empty`。\n *\n * 🔴 分开返回而不是抛:一张码表的响应畸形不该让另外 71 张跟着丢。\n *\n * 🔴 `nonDict` 不是可有可无的第三个返回值 —— `content` 里混进非对象元素时,\n * 只过滤不上报会让差额只表现为「计数 3、取回 2、partial」,顾问会照着去查 RBAC,\n * 而真相是响应里有个字符串。丢什么必须有出处。\n *\n * ⚠️ **布尔那一档在 TS 里与 Python 不同源,但判据照留**:Python 里 `isinstance(True, int)`\n * 为真,`totalElements: true` 会被当成「共 1 行」;JS 的 `typeof true === 'boolean'`,\n * `Number.isInteger(true)` 本来就是 false。⇒ 这里**不是**在补那个 Python 陷阱,\n * 而是把「必须是非负整数」这条正面判据写全 —— 布尔、浮点、字符串、负数一并拒。\n */\nfunction readContent(body: unknown): ContentRead {\n if (!isRecord(body)) return { rows: null, total: null, nonDict: 0 };\n const rows = body.content;\n const total = body.totalElements;\n if (!Array.isArray(rows)) return { rows: null, total: null, nonDict: 0 };\n if (typeof total !== 'number' || !Number.isInteger(total) || total < 0) {\n return { rows: null, total: null, nonDict: 0 };\n }\n const kept = rows.filter((r): r is Record<string, unknown> => isRecord(r));\n return { rows: kept, total, nonDict: rows.length - kept.length };\n}\n\nexport interface TruncatedBy {\n limit: 'requests' | 'wallclock';\n limitValue: number;\n requestsSent: number;\n elapsedSeconds: number;\n stoppedAtPage: number;\n rowsRetrieved: number;\n totalElements: number;\n}\n\nexport interface CodeTable {\n model: string;\n crawled: boolean;\n completeness: (typeof COMPLETENESS)[number];\n totalElements: number | null;\n rowsRetrieved: number;\n rows: Array<Record<string, unknown>>;\n probeStatus: number;\n probeError: string;\n truncatedBy: TruncatedBy | null;\n wording: string;\n}\n\nexport interface CollectCodeTablesOptions {\n maxRequests?: number;\n maxSeconds?: number;\n /**\n * 🔴 **可注入的单调时钟**(默认 `performance.now()` 的秒数),只为让墙钟上限那条守卫\n * 不必靠 sleep 去撞。用 sleep 造的用例,其判据依赖「调度器按时唤醒我」这个没人保证的\n * 前提 —— 负载一高就随机红,然后被放宽阈值,正是「判据必须能自证前提成立」记的那条病。\n * 注入后判据变成「墙钟走到 X 就必须停」。\n * ⚠️ 用单调时钟不用墙上时间:后者会被校时往回拨,一次回拨就能让「已经花了多久」\n * 算出负数,上限于是永不触发。\n */\n clock?: () => number;\n}\n\n/**\n * → `{tables, problems}`。`tables` 以 modelKey 为键,每值是一份可直接落盘的快照。\n *\n * 🔴 **问题文案只带 HTTP status 与响应分类,不解析服务端错误体**(显式取舍)。\n * 实测后端有**两套**错误信封,顶层都有 `code` 但语义相反:鉴权/框架层的 `code` 是数字\n * 状态码,业务层的 `code` 是字符串枚举、人话在 `details` 里。只认一套,另一套就会被读成\n * 「服务端说 code=400」这类**语法完好、指向错误**的文案 —— 比不带理由更坏。\n * 而响应分类(unauthorized / forbidden / network / server-error / bad-response)\n * 已经是决定顾问下一步动作的那一维,够用。\n */\nexport async function collectCodeTables(\n probe: ReconProbe,\n modelEntries: unknown[],\n opts: CollectCodeTablesOptions = {},\n): Promise<{ tables: Record<string, CodeTable>; problems: string[] }> {\n const maxRequests = opts.maxRequests ?? MAX_REQUESTS_PER_TABLE;\n const maxSeconds = opts.maxSeconds ?? MAX_SECONDS_PER_TABLE;\n const tick = opts.clock ?? (() => performance.now() / 1000);\n const post = probe.post;\n if (!post) throw new Error('探针没有 post 能力,取不了 list/dsl —— 这是编排的配置错,不是数据问题');\n\n const tables: Record<string, CodeTable> = {};\n const problems: string[] = [];\n const keys: string[] = [];\n for (const entry of modelEntries) {\n if (!isRecord(entry) || entry.type !== DYNAMIC_TYPE) continue;\n const key = entry.modelKey;\n // 🔴 key 纪律与 `extractFieldRows` / `refEdges` 逐字一致:**非字符串也要丢**。\n // 只判真假会让 `modelKey: 7` 活着进来,于是 ① 下面排序时当场类型错;\n // ② 不崩的时候更坏 —— 它会被拼进 URL 路径,产出一个谁也对不上的产物文件名。\n if (typeof key !== 'string' || !key) {\n // 静默跳过会让「名册里有个没名字的 DYNAMIC 条目」与「名册里压根没有它」同形。\n problems.push(`名册里有一条 type=DYNAMIC 的条目但 modelKey 不可用(${JSON.stringify(key)}),已跳过`);\n continue;\n }\n keys.push(key);\n }\n\n for (const key of [...new Set(keys)].sort()) {\n const path = listPath(key);\n // 🔴 每表预算从**这张表的第一发**开始算,计数探针也算一次请求:\n // 上限的语义是「这一张表最多花多少」,把探针排除在外会让「上限 1」这种极端配置\n // 变成「探针 + 1 页」,与常量名说的不是一回事。\n const started = tick();\n let requests = 0;\n let truncatedBy: TruncatedBy | null = null;\n // ① 先探计数。🔴 计数与取数必须在**同一轮里紧邻**发出:中间隔了别的工作\n // (比如把 72 张表的计数一次批量探完再回头取页),「取满了没有」这个判据的前提\n // (这中间总数没变)就不成立了 —— 判据会在别人写入的时候安静地说谎。\n const probeOut: Outcome = await post.call(probe, path, { page: 0, size: 0 });\n requests += 1;\n let total: number | null = null;\n let reason: string | null = null;\n if (probeOut.error !== 'ok') {\n reason = `计数探针失败(HTTP ${probeOut.status} / ${probeOut.error})`;\n } else {\n total = readContent(probeOut.body).total;\n // 🔴 「没答」与「答了但形状用不了」都落 unavailable(都拿不到计数),但文案必须分开:\n // 合并成一句「计数探针失败」会让顾问对着一个 HTTP 200 去查 VPN 和权限。\n reason = total !== null ? null\n : `计数探针答了 HTTP ${probeOut.status} 但响应形状不对(content 不是数组 / totalElements 不是非负整数)`;\n }\n if (total === null) {\n problems.push(`${key}: ${reason},本码表标 unavailable`);\n tables[key] = {\n model: key, crawled: false, completeness: 'unavailable',\n totalElements: null, rowsRetrieved: 0, rows: [],\n probeStatus: probeOut.status, probeError: probeOut.error,\n // 🔴 恒发键:这一档是「连计数都没读到」,与「取数时撞了上限」是两回事,\n // 缺键会被读成「这个工具还不懂上限」。\n truncatedBy: null, wording: WORDING,\n };\n continue;\n }\n\n // ② 逐页取。🔴 用 totalElements 定页数,而不是「取到空页就停」——\n // 中途一页返空(网关截断)会被当成正常结束,静默少读一半。\n const rows: Array<Record<string, unknown>> = [];\n let page = 0;\n let failed: string | null = null;\n // 🔴 熔断用:已见过的行身份。它必须在**循环内**发挥作用 —— 循环结束后才算的重复检测\n // 在「服务端忽略 page + totalElements 是脏数据 9,000,000」这种组合下**永远轮不到执行**:\n // 每页 2 行、循环 450 万次请求,现场表现是「工具卡死、无任何输出」。\n const seenIds = new Set<string>();\n let prevPage: string | null = null;\n while (rows.length < total) {\n // 🔴 上限判在**发请求之前**:语义是「不许再多发一发」,而不是「发完再看超没超」。\n // 后者每次都会比上限多一发,且「上限 = N」在不同表上意味着 N 或 N+1 次请求,\n // 顾问按常量算预算就永远对不上。\n // ⚠️ 它与下面的分页判据是**另一个维度**:那边判「服务端行为不对」,这里判\n // 「工具自己的预算用完了」。两者的现场处置不同(前者查服务端,后者调上限),\n // 所以谁先命中就由谁收尾,而 `truncatedBy` 只在**本条**命中时才非空。\n const elapsed = tick() - started;\n if (requests >= maxRequests || elapsed >= maxSeconds) {\n const which = requests >= maxRequests ? 'requests' : 'wallclock';\n truncatedBy = {\n limit: which,\n limitValue: which === 'requests' ? maxRequests : maxSeconds,\n requestsSent: requests,\n elapsedSeconds: Math.round(elapsed * 1000) / 1000,\n stoppedAtPage: page,\n rowsRetrieved: rows.length,\n totalElements: total,\n };\n failed =\n `触发工具的每表${which === 'requests' ? '请求' : '墙钟'}上限(` +\n `${which === 'requests' ? `已发 ${requests} 次请求` : `已花 ${elapsed.toFixed(3)}s`}),` +\n `停在第 ${page} 页,已收 ${rows.length} 行 / 计数 ${total} 行,` +\n '本表标 partial。**这是工具的截断,不是服务端的毛病** —— ' +\n `调大 valuedomain.${which === 'requests' ? 'MAX_REQUESTS_PER_TABLE' : 'MAX_SECONDS_PER_TABLE'} ` +\n `或给 collectCodeTables 传 ${which === 'requests' ? 'maxRequests' : 'maxSeconds'} 后重跑就能取全`;\n break;\n }\n const out: Outcome = await post.call(probe, path, { page, size: PAGE_SIZE });\n requests += 1;\n if (out.error !== 'ok') {\n failed = `第 ${page} 页失败(HTTP ${out.status} / ${out.error})`;\n break;\n }\n const { rows: got, total: pageTotal, nonDict } = readContent(out.body);\n if (got === null) {\n failed = `第 ${page} 页响应形状不对`;\n break;\n }\n // 🔴 每页响应自带的 `totalElements` 是**这个前提唯一的自证材料**,不许丢。\n // 上面那句「计数与取数紧邻发出」只是把窗口**缩小**,并没有**校验**「这一轮里总数没变」\n // —— 而码表恰恰是实施期顾问一边导码值、一边跑 recon 的对象。\n // 实测形态:探针探到 1000,取数期间有人插了 500 行,两页各 500 行且 id 全不重复\n // ⇒ id 重复守卫抓不到、`rows.length === total` 成立 ⇒ 产物宣布 visible-complete、\n // problems 全空,而实际只有 2/3 的码值 —— 而 visible-complete 正是「放行生成写脚本」那一档。\n //\n // 🔴 观测与处置分开:先把「本页有没有推进」**算出来**,再决定谁来收尾。\n // 放在计数变化之后判的话,两个症状同时存在时熔断那句**永远不会出现** ——\n // 顾问被指向「有人在写这张码表」,而现场同时还有「服务端忽略了 page」这条更硬的病。\n //\n // 🔴 熔断:本页**一行新的都没带来** ⇒ 再翻多少页也不会有进展,立刻停。两条判据互补:\n // ① 全部可判身份的行:本页 id 全是见过的 ⇒ 服务端在重复发同一页;\n // ② 一行都没有 id(① 判不了):本页内容与上一页**逐字相同** ⇒ 同上。\n // 🔴 ① 要求本页**每一行都可判身份**才敢下结论。少了这个前提就会假阳性:\n // 一页里只有 1 行带 id 且是旧的、其余 499 行是**全新的无 id 行** ⇒ 子集成立 ⇒ 熔断,\n // 止步 500 行,还报「服务端可能忽略了 page」—— 而服务端**根本没有**忽略。\n // ⚠️ 由此留下的边界:混合页且「有 id 的行全旧、无 id 的行也没推进」时两条都不触发,\n // 此时仍由 `total` 兜底。熔断的**触发**是可靠的,但它**不触发**不等于分页正常。\n //\n // ⚠️ ② 在 TS 里必须显式深比较:JS 的 `===` 对两个内容相同的数组是 false,\n // 直接照抄 Python 的 `got == prev_page` 会让这一支**永不触发**。用 JSON 序列化比。\n const pageIds = new Set(got.filter((r) => typeof r.id === 'string').map((r) => r.id as string));\n const serialized = JSON.stringify(got);\n const allIdentifiable = pageIds.size > 0 && pageIds.size === got.length;\n const noProgress =\n got.length > 0 &&\n ((allIdentifiable && [...pageIds].every((i) => seenIds.has(i))) ||\n (pageIds.size === 0 && prevPage !== null && serialized === prevPage));\n // ⚠️ 第 0 页上 `noProgress` 恒为 false,这不是漏判:那时**还没有证据**能说分页推没推进。\n // 所以计数变化在第 0 页触发时只报计数变化,不许顺口猜「分页也坏了」。\n if (pageTotal !== total) {\n failed = `取数期间计数从 ${total} 变成 ${pageTotal}(有人在写这张码表?本轮快照不完整)`;\n // 两个症状都留痕:它们指向的现场原因不同,只报一个会把顾问引偏。\n if (noProgress) failed += ';同一页还观察到分页没有推进(服务端可能也忽略了 page 参数)';\n break;\n }\n if (nonDict) {\n // 不 break:这一页里正常的行照收,但差额的出处要留下。\n problems.push(`${key}: 第 ${page} 页有 ${nonDict} 个非对象元素,未计入 rows`);\n }\n if (got.length === 0) {\n failed = `第 ${page} 页返空但还差 ${total - rows.length} 行`;\n break;\n }\n if (noProgress) {\n failed =\n `第 ${page} 页没有带来任何新行(服务端可能忽略了 page 参数),` +\n `已熔断,止步 ${rows.length} 行 / 计数 ${total} 行`;\n break;\n }\n for (const i of pageIds) seenIds.add(i);\n prevPage = serialized;\n rows.push(...got);\n page += 1;\n }\n\n // 🔴 前提要显式断言。`rows.length === total ⇒ 取满了` 这个判据依赖一个没人保证的前提:\n // **各页不重叠**。服务端忽略 `page` 参数(或反代把分页参数吃掉)时,同一页会被取回 N 遍,\n // 行数照样凑够 total,于是产物宣布 visible-complete,而里面是 N 份重复、\n // 缺了 (N-1)/N 的真实取值 —— 顾问拿它做对照表,缺的那些码值一个都对不上,\n // 且现场没有任何迹象指向分页。这条在写本模块时**实际发生过**。\n // ⚠️ 只能校验带字符串 `id` 的行 —— 没有 `id` 就没有身份,这里不替它编一个,\n // 也不因此声称「查过了」。\n // ⚠️ 这一条与循环里的熔断**不是重复**:熔断抓「整页都没有新行」(否则连不到这里就已经\n // 卡死几百万次请求),本条抓「每页都有一些新行、但页与页之间部分重叠」——\n // 那种情况循环能正常结束,只有事后按身份去重才看得出来。\n const collectedIds = rows.filter((r) => typeof r.id === 'string').map((r) => r.id as string);\n const duplicated = collectedIds.length - new Set(collectedIds).size;\n const complete = failed === null && rows.length === total && !duplicated ? 'visible-complete' : 'partial';\n if (duplicated) {\n problems.push(\n `${key}: 取回的 ${rows.length} 行里有 ${duplicated} 行 id 重复,分页很可能没生效` +\n '(服务端忽略了 page?),行数凑够 totalElements 也不算取满',\n );\n }\n if (failed) {\n problems.push(`${key}: ${failed}`);\n } else if (rows.length !== total) {\n // 取回数 < 计数而每页都成功:RBAC 裁剪先于分页与 count 时的形态。\n problems.push(`${key}: 取回 ${rows.length} 行但计数是 ${total} 行`);\n }\n tables[key] = {\n model: key, crawled: true, completeness: complete,\n totalElements: total, rowsRetrieved: rows.length, rows,\n probeStatus: probeOut.status, probeError: probeOut.error,\n // 🔴 两处落点之一(另一处是上面的 `problems`):**产物是被单独拷走的那一半**,\n // 只进 problems 的话,顾问 `cat valuedomain/CO_X.json` 看到 partial 却查不出是被谁截的。\n // `null` = 查过了没触发,对象 = 触发了(恒发键)。\n truncatedBy,\n wording: WORDING,\n };\n }\n return { tables, problems };\n}\n\nexport interface EnumDomainRow {\n model: string;\n field: string;\n fieldName: string;\n count: number;\n enums: Array<Record<string, unknown>>;\n nonDictElements: string[];\n}\n\n/**\n * 枚举字段的值域。\n *\n * 🔴 **元素原样落盘(verbatim),不挑键名**。实测 3179 个枚举元素有三种形状:\n * `{key,label,value}` × 2473、`+type` × 549、`+type,effect` × 157,三个键零空值。\n * 上一刀只留了 `value`+`label`,把 `key` 丢了;现在挑键名还会再丢 `type`/`effect`。\n * 「拿一个生产样本论证元素形态」是运气不是免疫 —— 换个元素形状就复发一次。\n *\n * ⚠️ 这里与 `fields.enumRows` **故意不同源**,两份产物的职责不同:\n * `index/fields.jsonl` 是**归一后**的可 grep 视图(缺 value 会退化补齐并打\n * `_kitValueSource` 认领标记);本产物是**逐字镜像**,不加工也不认领。\n * 要改的话请一起想清楚:把镜像也归一了,就再没有地方能看到产品原样发了什么。\n */\nexport function collectEnumDomains(metas: Record<string, unknown>): EnumDomainRow[] {\n const rows: EnumDomainRow[] = [];\n // 🔴 排序是产物可 diff 的前提:`metas` 的键序不恒定(定向重爬与全量爬的顺序不同),\n // 不排序两次跑同一个租户就 diff 出满屏噪声。\n for (const modelKey of Object.keys(metas).sort()) {\n // 判读走 `metaFields` 单一口径。这里不再记一份 problem 台账:\n // 同一份 metas 的坏载荷已由 `extractFieldRows` 记进 `dropped`,重复记会被加两次。\n const { fields } = metaFields(metas[modelKey]);\n for (const field of fields) {\n if (!isRecord(field)) continue;\n const fieldKey = field.key;\n // 🔴 与 `extractFieldRows` 的 key 判据逐字一致:**非字符串的 key 也要丢**。\n // 只判真假的话,`key: 7` 这个字段在 index/fields.jsonl 里被丢掉、在 `_enums.jsonl`\n // 里却存在 —— 同一份 metas 的两个消费者各判各的,两份产物对不上账,且谁也不报错。\n if (typeof fieldKey !== 'string' || !fieldKey) continue;\n const enums = field.enums;\n if (!Array.isArray(enums) || enums.length === 0) continue;\n // 浅拷贝:同一份 metas 还要喂给 fields / refs / mapping,直接挂原对象会让下游改产物时\n // 串改到 metas,而串扰在产物里完全看不出来。\n const kept = enums.filter(isRecord).map((e) => ({ ...e }));\n rows.push({\n model: modelKey,\n field: fieldKey,\n fieldName: (typeof field.name === 'string' && field.name) || '',\n count: kept.length,\n enums: kept,\n // 🔴 非对象元素进不了 `enums`(那一列的契约是「每项都是对象」),但也不许无痕消失\n // —— 否则「这个字段的枚举少了一项」这件事没有任何出处。\n // 实测 3179 条全是对象,这一档从未观测到;**恒发空列表**而不是条件出键:\n // 「有这一列且为空」与「没这一列」是两个值,后者读起来像「没检查过」。\n nonDictElements: enums.filter((e) => !isRecord(e)).map((e) => JSON.stringify(e) ?? String(e)),\n });\n }\n }\n return rows;\n}\n\n/**\n * 🔴 `valuedomain/` 这一层**保留的文件名**:索引与枚举镜像。\n * ⚠️ **落盘那两处用的仍是字面量**,不是从这里取 —— 说清楚免得下一个人以为改这里就够了。\n * ⇒ 这里是**判据与文案**的单一来源;将来再加工具自己的产物文件,两处都要动。\n */\nexport const RESERVED_FILENAMES = ['_index.json', '_enums.jsonl'] as const;\n\nconst PATH_SEPARATORS = ['/', String.fromCharCode(92)];\n\n/**\n * 码表 modelKey 能不能安全地当 `valuedomain/<key>.json` 用 → 不能则给出人话理由。\n *\n * 🔴 **抽成模块级函数是承重的,不是整理代码**:`writeValuedomain` 用它决定「写不写」,\n * 编排层用它决定「这张表的文件到底是不是它自己的快照」。两处必须问**同一个问题** ——\n * 各判各的正是一条真缺陷的成因:编排层原先只按「路径在不在 `written` 里」推导,\n * 而 `written` 里**恒有** `valuedomain/_index.json`(索引自己写的那一份),\n * 于是一张叫 `_index` 的码表被判成「文件写出来了」,mapping 照着发指针,\n * 两条对账链**都报干净**,而指针解析出来的是索引。\n *\n * 🔴 本地判据与落盘闸是**纵深防御的两层,职责不同**,别合并也别删:\n * 落盘闸抛异常(fail-loud,但调用方得接住、且理由是路径口径的);\n * 本地判据能把「哪张码表、为什么没落盘」**留痕进 `writeProblems`**,\n * 顾问在产物里才看得见 —— 那是他唯一的现场信息。\n *\n * 本地判据的射程是这三种(`..` 越目录那一档由落盘闸的分量判据守):\n * ① 路径分隔符:把快照塞进子目录,`ls valuedomain/` 按 modelKey 找不回来;\n * ② 前导 `.`:产出 `ls` 看不见的隐藏文件;\n * ③ 前导 `_`:`_index` / `_enums` 是**本目录工具自己的保留文件名**,\n * 一张叫 `_index` 的码表会把这份索引顶掉 —— 而顶掉之后 `writeProblems` 是空的、\n * 该表还照常出现在 `byCompleteness` 里,像正常落了盘一样。\n */\nexport function unsafeTableKeyReason(key: string): string | null {\n if (PATH_SEPARATORS.some((s) => key.includes(s))) {\n return `modelKey 含路径分隔符,落盘会写出 valuedomain/ 子目录:${JSON.stringify(key)}`;\n }\n if (key.startsWith('.')) return `modelKey 以 . 开头,会产出 ls 看不见的隐藏文件:${JSON.stringify(key)}`;\n if (key.startsWith('_')) {\n // 不只判 `_index` / `_enums` 两个具体名字:整段前缀留给工具,下一个 `_xxx` 产物\n // 才不必回来再补一条(而漏补的表现是静默顶掉那份新产物)。\n return (\n `modelKey 以 _ 开头,与本目录工具自己的保留文件名(${RESERVED_FILENAMES.join('、')})` +\n `同一命名空间,会顶掉它们:${JSON.stringify(key)}`\n );\n }\n return null;\n}\n\nexport interface WriteValuedomainOptions {\n identity?: unknown;\n modelEntriesSeen?: number | null;\n carryOver?: unknown;\n problems?: string[] | null;\n roundId?: string | null;\n}\n\n/**\n * 落盘:`valuedomain/<modelKey>.json` 每表一份 + `_enums.jsonl` + `_index.json`。\n *\n * 一表一文件而不是一个大 JSON:顾问在现场是按「客户 Excel 里这一列对应哪张码表」逐张查的,\n * 一张 3212 行的表塞进合集里会让每次查看都要加载全部 9417 行。\n *\n * 🔴 **逐张接异常,一张表的名字有毒不许炸掉整批**。落盘闸对越界的 rel 会抛;原先没接,\n * 于是一个越界 modelKey 会中断**整个**落盘 —— 另外 71 张表的产物、`_enums.jsonl`、\n * 连同整份 `_index.json` 一起消失。爆炸半径不对。\n *\n * 🔴 **只接越界那一档,别的照旧往上抛**。我们要的是「不让它炸掉整批」,**不是**「把它吞掉」\n * —— 所以必须留痕进 `writeProblems`。接住一切会把磁盘满 / 只读挂载 / 权限一并吞成一条\n * 产物问题,而那类故障下**整批**都写不出去,报成「这一张有问题」是把系统性故障伪装成个例。\n *\n * 🔴 **写入顺序是承重的**:码表 → `_enums.jsonl` → `_index.json` **最后**。\n * 索引最后写才能把 `writeProblems` 装进去;否则一张表静默缺席,与「这张表压根不存在」\n * 在产物里同形 —— 顾问翻遍 `valuedomain/` 找不到某张码表,无从知道是没爬到、\n * 没落盘、还是产品里真没有。\n */\nexport function writeValuedomain(\n layout: ReconLayout,\n codeTables: Record<string, CodeTable>,\n enums: EnumDomainRow[],\n opts: WriteValuedomainOptions = {},\n): { written: string[]; writeProblems: string[] } {\n const written: string[] = [];\n const writeProblems: string[] = [];\n for (const key of Object.keys(codeTables).sort()) {\n const reason = unsafeTableKeyReason(key);\n if (reason !== null) {\n writeProblems.push(`${key}: ${reason},已拒绝`);\n continue;\n }\n try {\n written.push(layout.writeJson(`valuedomain/${key}.json`, codeTables[key]));\n } catch (err) {\n // 🔴 **只接越界那一档,别的照旧往上抛**(移植时这里写漏过一次,接住了一切)。\n //\n // ⚠️ **可达性说准(实测,不是推的)**:把量到的形态逐个打一遍 ——\n // 落盘闸判越界的只有 `../evil` / `../../evil` 两种,而它们**本地判据已经先拦掉了**;\n // 本地放行的(`foo..bar` / `CO_OK`)落盘闸一律放行。\n // ⇒ 下面那句 `writeProblems.push` 在当前实现下**走不到**。留着它不是为了覆盖率,\n // 是因为落盘闸在另一个模块、它的判据变过(曾经不拦 `..` 分量),\n // 两层的射程一旦再错开,这里就是唯一不让整批炸掉的地方。\n // 而 `throw` 那一半**是被覆盖的**:超长 modelKey 触发的 ENAMETOOLONG 实测抛的是\n // 普通 Error(不是越界),照这条原样往上抛 —— 那类是系统性故障,不该逐条吞掉。\n // 落盘闸的拒绝是**故意的安全闸**,我们要的是「不炸掉整批」而不是「吞掉」。\n // 接住一切会把磁盘满 / 只读挂载 / 权限一并吞成一条产物问题 ——\n // 而那类故障下**整批**都写不出去,报成「这一张有问题」是把系统性故障伪装成个例。\n if (!(err instanceof LayoutContainmentError)) throw err;\n // 原始异常消息一起带出来:里面有被拒的 rel 与产物根,是顾问唯一的现场信息。\n writeProblems.push(`${key}: 落盘被拒,本码表没有产物文件(${(err as Error).message})`);\n }\n }\n written.push(layout.writeJsonl('valuedomain/_enums.jsonl', enums));\n // 🔴 索引里带取数时的权限画像:同一份产物换个角色跑出来的可见范围不同,\n // 不记身份就没法判断两份产物为什么不一样。\n written.push(\n layout.writeJson('valuedomain/_index.json', {\n crawled: true,\n wording: WORDING,\n // 🔴 轮次身份。这份索引会被**单独拷走**,而一次中断会让它留在上一轮 ——\n // 那时 `valuedomain/` 里缺某张码表与「那张表真的没有」同形。\n // `null` = 调用方没告诉我(与 identity / modelEntriesSeen 同口径),恒发键。\n roundId: opts.roundId ?? null,\n // 🔴 `identity || {}` 会把「调用方没传身份」和「身份是空的」压成同一个值,\n // 而它们的下一步不同(前者去补工具的登录,后者去问服务端为什么不给 user)。\n identity: isRecord(opts.identity) ? opts.identity : null,\n codeTableCount: Object.keys(codeTables).length,\n enumFieldCount: enums.length,\n // 🔴 名册里一共看到几条。上游名册加载失败给出空数组时,产物会与\n // 「名册 742 条里一条 DYNAMIC 都没有」**逐字节同形**。\n // `null` = 调用方没告诉我(与 identity 同口径),`0` = 名册确实是空的。\n modelEntriesSeen: opts.modelEntriesSeen ?? null,\n // 🔴 定向重爬披露。这份产物**会被单独拷走**,而 `_enums.jsonl` 里非重爬模型的枚举\n // 来自**沿用的 meta** —— 不在这份索引里说一声,拿走 `valuedomain/` 的人就会把整册\n // 都当成本轮观测到的。**恒发键**:缺席不许兼职「本轮是全量」。\n [KIT_CARRY_OVER_KEY]: opts.carryOver ?? null,\n // 🔴 四档全列,产不出来的那一档给空数组而不是缺键:下游据这份索引决定放不放行写入,\n // 缺键会被读成「这个工具还不懂这一档」,空数组才是「懂,且本轮一个都没有」。\n byCompleteness: Object.fromEntries(\n COMPLETENESS.map((c) => [\n c,\n Object.entries(codeTables).filter(([, v]) => v.completeness === c).map(([k]) => k).sort(),\n ]),\n ),\n // 🔴 `byCompleteness` 里有名字、这里却列着的表 = 爬到了但没落盘。\n // 两者一起看才知道「找不到某张码表的文件」是哪一种原因。\n writeProblems,\n // 🔴 **取数期**的台账,与上面那份**落盘期**的不是一回事,合并会把「有这张表但内容残」\n // 和「压根没有这份文件」压成一句话。明细与摘要两处都要有,缺一份都等于没记。\n // `null` = 调用方没告诉我,`[]` = 对过账且一条都没有。\n problems: opts.problems ?? null,\n }),\n );\n return { written, writeProblems };\n}\n","/**\n * A 类七子集**逐类**派生 —— 门 0 的「环境事实」那一半。\n *\n * 逐类而非合并成一个大集合:变异自证要能定位到**是哪一类没守住**,合并了就只知道\n * 「A 类漏了」。\n *\n * ## 每类的来源(键名与 recon 的写入方逐条对齐)\n *\n * | 子集 | 产物 | 取哪个键 | 写入方 |\n * |---|---|---|---|\n * | `fieldKey` | `index/fields.jsonl` | `key` | `recon/fields.ts` |\n * | `fieldLabel` | `index/fields.jsonl` | `name`(中文 label) | 同上 |\n * | `caliber` | `index/fields.jsonl` | `synonyms[]` | 同上 |\n * | `enumValue` | `valuedomain/_enums.jsonl` | `enums[]` 元素的 `value` / `label` / `key` | `recon/valuedomain.ts` |\n * | `modelName` | `models.json` | `models[].modelKey` **与** `models[].name` | `recon/crawl.ts` |\n * | `codeValue` | `valuedomain/<模型>.json` | `rows[]` 里**递归**取到的全部标量值 | `recon/valuedomain.ts` |\n * | `naturalKey` | `work/mapping.json` | `models[].businessKey.fields` | 顾问手写 |\n *\n * 🔴 **一类多来源时,每个来源各要一条自己的判据**。「这一类非空」守不住任何一个半边:\n * `modelName` 去掉英文 key 那半、`codeValue` 去掉 `code` 那半、`enumValue` 去掉\n * `value` 或 `key` 那半,集合都还非空,门却不响。\n * ⚠️ 判据写对还不够 —— **夹具必须在这几个来源上真分岔**(枚举元素的 `key` 与 `value`\n * 逐个相同的夹具,会让「去掉 `key`」与「去掉 `value`」两种变异全绿)。\n *\n * ## 台账分两档\n *\n * · **类级** `CLASS_SCOPE`:**这一类此刻无人守**。`reason` 再分两档 ——\n * `REASON_UNREAD`(产物/键根本没读到)与 `REASON_EMPTY`(读到了,一条取值都没派生出来)。\n * · **条目级** `ENTRY_SCOPE`:某一行 / 某一份码表有问题,**这一类整体仍然有人守**。\n *\n * 不变式(`unguardedKinds`):**类级点名的类集合 == 集合为空的类集合**,两向都成立。\n * 它由末尾那一趟补账**构造性地**保证,所以真正有判别力的是 `reason` 分档对不对,\n * 以及「多点一类」那个方向(给一个**还有内容**的类记类级台账 ⇒ 不变式当场破)。\n *\n * ⚠️ `{\"models\": []}` 会有一条 `REASON_EMPTY` 的类级台账 —— 它不是「报错」,\n * 是「这一类此刻无人守」这件事的登记。「缺键」(根本没查到)与「空数组」(查过且为空)\n * 分在 `reason` 上,不分在「报不报」上;两者塌成一件事时,六种 dict 形状产出逐字相同的\n * 「类空 + 台账零字」,而它们的下一步完全不同。\n *\n * ## 🔴 缺口(本模块覆盖不到的,写死在这里,不许在别处说成已覆盖)\n *\n * · `caliber` 只取 `synonyms`(逐条、机器可读)。字段 `description` 是**散文**,\n * 从散文里抽口径术语没有机械判据 —— **不覆盖**。别把 `caliber` 说成「已覆盖口径」。\n * · `naturalKey` 只取映射工件**显式声明**的业务键;工具**不推导**。\n * · `enumValue` 只取元素的 `value` / `label` / `key`。`type` / `effect` 是产品侧的\n * 结构属性,不是租户事实,**不取**。⚠️ 这一档与 `codeValue` 的「全部标量值」\n * **故意不同源**:枚举元素的键是产品契约里数得清的那几个,码表 `rows` 是服务端\n * **逐字**的业务行、键名数不清,挑键名就等于赌下一个环境不会多一个键。\n */\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';\nimport * as path from 'node:path';\n\nimport { modelEntries, modelKeys } from '../fingerprint';\nimport { RESERVED_FILENAMES } from '../recon/valuedomain';\nimport { readBusinessKeyFields, type MappingArtifactError } from './mapping-artifact';\n\nexport const ACLASS_KINDS = [\n 'fieldKey',\n 'fieldLabel',\n 'enumValue',\n 'modelName',\n 'codeValue',\n 'naturalKey',\n 'caliber',\n] as const;\nexport type AclassKind = (typeof ACLASS_KINDS)[number];\n\n/** 产物相对路径。与写入方是同一串字面量。 */\nexport const FIELDS_INDEX = ['index', 'fields.jsonl'];\nexport const ENUM_MIRROR = ['valuedomain', '_enums.jsonl'];\nexport const MODEL_ROSTER = ['models.json'];\nexport const VALUE_DOMAIN_DIR = 'valuedomain';\n\n/** 枚举元素里**属于租户事实**的那几个键。 */\nexport const ENUM_ELEMENT_KEYS = ['value', 'label', 'key'] as const;\n\n/** 只用于**报错文案**指路;真路径由调用方给。 */\nexport const MAPPING_ARTIFACT_HINT = 'work/mapping.json';\n\nexport const CLASS_SCOPE = 'class';\nexport const ENTRY_SCOPE = 'entry';\n/**\n * 🔴 取值域要有个**唯一的**出处:`scope` 写错一个字母,那条类级台账就从\n * `unguardedKinds` 里**静默消失** —— 症状与「这一类有人守」逐字相同。\n */\nexport const SCOPES = [CLASS_SCOPE, ENTRY_SCOPE] as const;\nexport type AclassScope = (typeof SCOPES)[number];\n\nexport const REASON_UNREAD = 'unread';\nexport const REASON_EMPTY = 'empty';\n/**\n * **类级台账的 reason 取值域**。它存在的唯一理由是**给下游一个能绑的键集**:\n * `scan.ts` 要按这两档说出「为什么这一类是空的」。下游查表用 `[]` 而不是 `.get(…, '')`\n * —— 这里多一档而下游没跟上 ⇒ 当场炸,不是静默少一句话。\n */\nexport const CLASS_REASONS = [REASON_UNREAD, REASON_EMPTY] as const;\nexport type ClassReason = (typeof CLASS_REASONS)[number];\n\n/**\n * 一条台账。\n *\n * 🔴 台账是**结构**不是字符串:判据要按「类级 / 条目级」分档,而分档信息一旦只活在\n * 中文消息里,判据就得去解析散文 —— 改一个字就悄悄失效。\n */\nexport interface AclassProblem {\n readonly scope: string;\n readonly reason: string;\n readonly kinds: readonly AclassKind[];\n readonly message: string;\n}\n\nexport type Aclass = Record<AclassKind, Set<string>>;\n\n/**\n * → 类级台账点名的类集合。\n *\n * 🔴 **认不出的 `scope` 一律抛,不许当成条目级悄悄跳过**:`'clsss'` 这种手误会让那一条\n * 从这个集合里消失,而「这一类没人守」与「这一类有人守」在返回值上**逐字同形**。\n */\nexport function unguardedKinds(problems: readonly AclassProblem[]): Set<AclassKind> {\n const out = new Set<AclassKind>();\n for (const problem of problems) {\n if (!(SCOPES as readonly string[]).includes(problem.scope)) {\n throw new Error(\n `台账的 scope 只有 ${SCOPES.join('/')} 两个取值,拿到的是 ` +\n `${JSON.stringify(problem.scope)}(${problem.message})`,\n );\n }\n if (problem.scope === CLASS_SCOPE) for (const kind of problem.kinds) out.add(kind);\n }\n return out;\n}\n\nfunction unguardedWord(kinds: readonly AclassKind[]): string {\n return kinds.join('/');\n}\n\n/**\n * 标量 → 集合里的一个 token;空值 / 非标量返 `null`。\n *\n * 非字符串标量也收:这几份产物是**逐字镜像**,服务端发数字取值是形状问题,\n * 不是本模块的口径问题 —— 丢掉它,那个取值这一类恒不判红。\n * ⚠️ `boolean` 排除:收下去是 `\"true\"`,那不是取值。\n */\nfunction token(value: unknown): string | null {\n if (typeof value === 'string') return value === '' ? null : value;\n if (typeof value === 'number' && Number.isFinite(value)) return String(value);\n return null;\n}\n\n/**\n * → `value` 里**递归**取到的全部标量 token。\n *\n * 🔴 **非标量不许静默丢弃**:码表行里的 i18n Map 与数组是 HCM 的真实形态,\n * 只取顶层标量时 `{\"nameI18n\": {\"zh-CN\": \"中国\", \"en\": \"China\"}}` 里的两个取值\n * 一个都不进集合、也不留台账 —— 门 0 对它们恒绿,而它们正是会被抄进 skill 正文的\n * 那一档中文。⚠️ 只取**值**,不取 dict 的**键**(`zh-CN` 是结构,不是租户事实)。\n */\nfunction scalarsWithin(value: unknown): string[] {\n if (Array.isArray(value)) return value.flatMap(scalarsWithin);\n if (typeof value === 'object' && value !== null) {\n return Object.values(value as Record<string, unknown>).flatMap(scalarsWithin);\n }\n const one = token(value);\n return one === null ? [] : [one];\n}\n\n/**\n * → 正文;读不到就记一条**点名这几类**的台账并返回 `null`。\n *\n * 🔴 `scope` **由调用点给,没有缺省**:读取工具看不出「这一份产物是不是这一类的全部来源」。\n * `index/fields.jsonl` 只有一份 ⇒ 读不到就是**类级**;`valuedomain/<模型>.json` 有很多份\n * ⇒ 一份读不到只是**条目级**。在工具里硬写 `CLASS_SCOPE` 时,两张码表(一好一坏)会让\n * 台账说出「codeValue 这一类此刻无人守」这句**假话**,而那一类明明还有别的 token。\n */\nfunction readText(\n file: string,\n kinds: readonly AclassKind[],\n scope: AclassScope,\n problems: AclassProblem[],\n): string | null {\n try {\n return readFileSync(file, 'utf8');\n } catch (e) {\n const code = (e as NodeJS.ErrnoException)?.code;\n const name = path.basename(file);\n problems.push({\n scope,\n reason: REASON_UNREAD,\n kinds,\n message:\n code === 'ENOENT'\n ? `缺产物 ${name} ⇒ ${unguardedWord(kinds)} 这几类此刻无人守`\n : `产物 ${name} 读不动(${String(e)})⇒ ${unguardedWord(kinds)} 这几类此刻无人守`,\n });\n return null;\n }\n}\n\n/** → 行对象清单。坏行逐条留**条目级**痕,不整份放弃,也不静默跳过。 */\nfunction readJsonl(\n file: string,\n kinds: readonly AclassKind[],\n scope: AclassScope,\n problems: AclassProblem[],\n): Record<string, unknown>[] {\n const text = readText(file, kinds, scope, problems);\n if (text === null) return [];\n const name = path.basename(file);\n const rows: Record<string, unknown>[] = [];\n text.split('\\n').forEach((raw, i) => {\n const line = raw.trim();\n if (line === '') return;\n let row: unknown;\n try {\n row = JSON.parse(line);\n } catch (e) {\n problems.push({\n scope: ENTRY_SCOPE,\n reason: 'bad-line',\n kinds,\n message: `${name}:${i + 1} 不是合法 JSON(${String(e)})⇒ 这一行的取值没有进 ${unguardedWord(kinds)}`,\n });\n return;\n }\n if (typeof row !== 'object' || row === null || Array.isArray(row)) {\n problems.push({\n scope: ENTRY_SCOPE,\n reason: 'bad-line',\n kinds,\n message:\n `${name}:${i + 1} 不是对象(${row === null ? 'null' : typeof row})⇒ ` +\n `这一行的取值没有进 ${unguardedWord(kinds)}`,\n });\n return;\n }\n rows.push(row as Record<string, unknown>);\n });\n return rows;\n}\n\n/**\n * 「根本没读到」与「读到了、内容是合法的 JSON `null`」必须是**两个值**。\n * 两者共用 `null` 时,`null` 那一档**零 problem** 地滑过去:一份 `models.json` 写着 `null`,\n * `modelName` 整类此刻无人守,而台账上一个字都没有。\n */\nconst UNREADABLE = Symbol('unreadable');\n\nfunction readJson(\n file: string,\n kinds: readonly AclassKind[],\n scope: AclassScope,\n problems: AclassProblem[],\n): unknown | typeof UNREADABLE {\n const text = readText(file, kinds, scope, problems);\n if (text === null) return UNREADABLE;\n try {\n return JSON.parse(text);\n } catch (e) {\n problems.push({\n scope,\n reason: REASON_UNREAD,\n kinds,\n message: `产物 ${path.basename(file)} 不是合法 JSON(${String(e)})⇒ ${unguardedWord(kinds)} 这几类此刻无人守`,\n });\n return UNREADABLE;\n }\n}\n\n/**\n * 取 `payload[key]` 这个数组,并把**缺键**与**空数组**分成两件事。\n *\n * 🔴 **缺键 = 根本没查到**(载荷是 `{}`、或网关加了一层信封把它埋进别的键里)⇒ 必须留痕;\n * **键在但是空数组 = 查过且为空** ⇒ 这一趟没有任何异常,不在这里留痕\n * (那一类若因此为空,末尾那趟补账会按 `REASON_EMPTY` 登记)。\n */\nfunction listUnder(\n payload: unknown,\n key: string,\n file: string,\n kinds: readonly AclassKind[],\n scope: AclassScope,\n problems: AclassProblem[],\n): unknown[] {\n const name = path.basename(file);\n if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {\n problems.push({\n scope,\n reason: REASON_UNREAD,\n kinds,\n message:\n `产物 ${name} 不是对象(${payload === null ? 'null' : Array.isArray(payload) ? 'array' : typeof payload})` +\n `⇒ ${unguardedWord(kinds)} 这几类此刻无人守`,\n });\n return [];\n }\n const body = payload as Record<string, unknown>;\n if (!(key in body)) {\n problems.push({\n scope,\n reason: REASON_UNREAD,\n kinds,\n message:\n `产物 ${name} 里没有 ${key} 这个键(顶层键是 ${JSON.stringify(Object.keys(body).sort())})` +\n `⇒ ${unguardedWord(kinds)} 这几类此刻无人守`,\n });\n return [];\n }\n const value = body[key];\n if (!Array.isArray(value)) {\n problems.push({\n scope,\n reason: REASON_UNREAD,\n kinds,\n message:\n `产物 ${name} 的 ${key} 不是数组(${value === null ? 'null' : typeof value})` +\n `⇒ ${unguardedWord(kinds)} 这几类此刻无人守`,\n });\n return [];\n }\n return value;\n}\n\nexport interface DerivedAclass {\n aclass: Aclass;\n problems: AclassProblem[];\n}\n\n/**\n * → `(aclass, problems)`。`aclass[kind]` 是 `Set<string>`,七个键**恒在**。\n *\n * `root` = recon 产物根(`ReconLayout.dir` 那一层)。\n * `mappingArtifact` = 顾问手写的 `work/mapping.json` 路径;`null` 时 `naturalKey`\n * 这一类**没有来源**,此事进 problems。\n *\n * 🔴 七个键恒在(而不是「有内容才建键」):调用方按 `ACLASS_KINDS` 遍历时,\n * 「这一类是空的」与「根本没有这一类」必须是两个状态 —— 后者读起来像「没查过」。\n */\nexport function deriveAclass(root: string, mappingArtifact: string | null = null): DerivedAclass {\n const problems: AclassProblem[] = [];\n const out = Object.fromEntries(ACLASS_KINDS.map((k) => [k, new Set<string>()])) as Aclass;\n\n // ── ① fieldKey / fieldLabel / caliber ← index/fields.jsonl ──────────────────\n for (const row of readJsonl(\n path.join(root, ...FIELDS_INDEX),\n ['fieldKey', 'fieldLabel', 'caliber'],\n CLASS_SCOPE,\n problems,\n )) {\n const key = token(row.key);\n if (key !== null) out.fieldKey.add(key);\n const name = token(row.name);\n if (name !== null) out.fieldLabel.add(name);\n const synonyms = row.synonyms;\n for (const syn of Array.isArray(synonyms) ? synonyms : []) {\n const one = token(syn);\n if (one !== null) out.caliber.add(one);\n }\n }\n\n // ── ② enumValue ← valuedomain/_enums.jsonl 的 enums[] 元素 ──────────────────\n for (const row of readJsonl(\n path.join(root, ...ENUM_MIRROR),\n ['enumValue'],\n CLASS_SCOPE,\n problems,\n )) {\n const enums = row.enums;\n for (const element of Array.isArray(enums) ? enums : []) {\n // 非对象元素在写入方那边已被挑进 `nonDictElements` 留痕,这里不重复记账。\n if (typeof element !== 'object' || element === null || Array.isArray(element)) continue;\n for (const field of ENUM_ELEMENT_KEYS) {\n const one = token((element as Record<string, unknown>)[field]);\n if (one !== null) out.enumValue.add(one);\n }\n }\n }\n\n // ── ③ modelName ← models.json(走 `modelKeys` 这道唯一的门)─────────────────\n const rosterPath = path.join(root, ...MODEL_ROSTER);\n const roster = readJson(rosterPath, ['modelName'], CLASS_SCOPE, problems);\n if (roster !== UNREADABLE) {\n const rows = listUnder(roster, 'models', rosterPath, ['modelName'], CLASS_SCOPE, problems);\n const entries = modelEntries(roster);\n const keys = modelKeys(roster);\n for (const key of keys) out.modelName.add(key);\n // **中文模型名**一并进这一类:它与「中文 label 单独成类」的理由逐字相同 ——\n // 读起来像散文、租户可改,是最容易被顺手写进 skill 正文的形态。\n for (const entry of entries) {\n const name = token(entry.name);\n if (name !== null) out.modelName.add(name);\n }\n // 那道门只负责「不崩」,掉没掉东西由调用方各自留痕。**两种掉法各留各的痕**:\n // 元素不是对象(整条进不来)与条目没有 modelKey(英文 key 那一半进不来,\n // 中文名那一半可能还在)—— 塌成一条就分不出该查哪一头。\n if (rows.length !== entries.length) {\n problems.push({\n scope: ENTRY_SCOPE,\n reason: 'dropped-entries',\n kinds: ['modelName'],\n message:\n `models.json 里 ${rows.length} 条模型条目有 ${rows.length - entries.length} 条不是对象 ` +\n '⇒ 它们的模型名没有进 modelName',\n });\n }\n if (entries.length !== keys.length) {\n problems.push({\n scope: ENTRY_SCOPE,\n reason: 'dropped-keys',\n kinds: ['modelName'],\n message:\n `models.json 里 ${entries.length} 条模型条目有 ${entries.length - keys.length} 条没有 modelKey ` +\n '⇒ 它们的英文模型名没有进 modelName',\n });\n }\n }\n\n // ── ④ codeValue ← valuedomain/<模型>.json 的 rows[] ─────────────────────────\n const valueDomain = path.join(root, VALUE_DOMAIN_DIR);\n if (existsSync(valueDomain) && statSync(valueDomain).isDirectory()) {\n const tables = readdirSync(valueDomain)\n .filter((f) => f.endsWith('.json') && !(RESERVED_FILENAMES as readonly string[]).includes(f))\n .sort();\n for (const name of tables) {\n const file = path.join(valueDomain, name);\n // 🔴 单张码表读不出来只是**条目级**:别的码表照常派生,这一类整体仍然有人守。\n // 这个 `ENTRY_SCOPE` 要一路带进 `readJson` / `readText`。\n const payload = readJson(file, ['codeValue'], ENTRY_SCOPE, problems);\n if (payload === UNREADABLE) continue;\n for (const row of listUnder(payload, 'rows', file, ['codeValue'], ENTRY_SCOPE, problems)) {\n if (typeof row !== 'object' || row === null || Array.isArray(row)) {\n problems.push({\n scope: ENTRY_SCOPE,\n reason: 'bad-row',\n kinds: ['codeValue'],\n message:\n `${name} 的 rows 里有一个元素不是对象(${row === null ? 'null' : typeof row})` +\n '⇒ 它的取值没有进 codeValue',\n });\n continue;\n }\n // 取**全部标量值**(含 `id`):`rows` 是服务端逐字的业务行,键名数不清 ——\n // 挑键名就等于赌下一个环境不会多一个键,而漏掉的那个键恰恰是「换个环境就变」\n // 的环境事实。噪声(纯数字 / 单字符)归判红那一层的 token 口径,不在这里过滤。\n for (const value of scalarsWithin(row)) out.codeValue.add(value);\n }\n }\n } else {\n problems.push({\n scope: CLASS_SCOPE,\n reason: REASON_UNREAD,\n kinds: ['codeValue'],\n message: `缺产物目录 ${VALUE_DOMAIN_DIR}/ ⇒ codeValue 这一类此刻无人守`,\n });\n }\n\n // ── ⑤ naturalKey ← work/mapping.json 的 models[].businessKey.fields ─────────\n let businessKeys: string[][] | null = null;\n if (mappingArtifact === null) {\n problems.push({\n scope: CLASS_SCOPE,\n reason: REASON_UNREAD,\n kinds: ['naturalKey'],\n message: `没有指定映射工件 ⇒ naturalKey 这一类此刻无人守(把 ${MAPPING_ARTIFACT_HINT} 的路径传进来)`,\n });\n } else {\n try {\n businessKeys = readBusinessKeyFields(mappingArtifact);\n } catch (e) {\n const err = e as MappingArtifactError;\n problems.push({\n scope: CLASS_SCOPE,\n reason: REASON_UNREAD,\n kinds: ['naturalKey'],\n message: `映射工件读不动([${err.reason ?? 'unknown'}] ${err.message})⇒ naturalKey 这一类此刻无人守`,\n });\n }\n }\n for (const fields of businessKeys ?? []) {\n for (const field of fields) out.naturalKey.add(field);\n if (fields.length > 1) {\n // 复合业务键的**组合形态**自成一个 token:skill 正文里写死「按 code+orgUnitCode 唯一」\n // 与写死单个字段名是两件事,两件都要判得出来。\n out.naturalKey.add(fields.join('+'));\n }\n }\n\n // ── ⑥ 补账:空集合的类**一个不漏**地进类级台账(不变式在这里被构造出来)────────\n // 🔴 措辞不许越出观测:这里只知道「产物那一头读过了、这一类一条取值都没派生出来」,\n // 不知道是环境上真没有、还是某一份条目坏了 —— 后者的真因在同批的条目级台账里。\n const named = unguardedKinds(problems);\n for (const kind of ACLASS_KINDS) {\n if (out[kind].size === 0 && !named.has(kind)) {\n problems.push({\n scope: CLASS_SCOPE,\n reason: REASON_EMPTY,\n kinds: [kind],\n message:\n `${kind}:产物读到了,但一条取值都没派生出来 ⇒ 门 0 对这一类恒绿` +\n '(同批台账里若有条目级问题,真因在那里)',\n });\n }\n }\n\n return { aclass: out, problems };\n}\n","/**\n * 顾问手写的映射工件 `work/mapping.json` —— **只读 `businessKey.fields` 那一片**。\n *\n * 🔴 **射程写在明处**:这不是 kit `init/artifacts.py::load_mapping` 的移植。\n * 那一份是导入流水线的完整装载器(行为契约清单 §9.4,80 条),随 **ADR-319 刀 11**\n * 一起过来。门 0 只要 `models[].businessKey.fields` 这一片,而 `naturalKey` 这一类的\n * **唯一来源**就是它 —— 顾问手写、产品里没有任何接口查得到。\n *\n * ⚠️ **它比刀 11 那一份宽**,方向是刻意的:`source` / `columns` / `valueMap` 这些\n * 门 0 用不着的段,这里**不校验**。于是有一档工件「门 0 读得出业务键、`plan` 会拒收」。\n * 这个方向是安全的一侧 —— 门 0 多认出几个自然键,只会**多判红**;反过来(门 0 认不出\n * 而 `plan` 认得)才会让环境事实静默漏判。\n * 🔴 刀 11 落地时,这里换成调那一份,**不是**在这里补校验:两份解析各写各的,\n * 「夹具读得出来」会在验收面上成立、在真跑时不成立。\n *\n * 版本号照旧要判:`version` 对不上时**不许**「尽量按老版本读」—— 那会把新语义静默丢掉,\n * 而丢掉的恰恰可能是业务键本身。\n */\nimport { readFileSync } from 'node:fs';\n\n/** 工件契约版本。与 kit `init/artifacts.py::VERSION` 同值,刀 11 合流时以那一份为准。 */\nexport const MAPPING_ARTIFACT_VERSION = 1;\n\n/** 读不下去。`reason` 给程序判、`message` 给人读,两者都不许省。 */\nexport class MappingArtifactError extends Error {\n constructor(\n readonly reason: string,\n override readonly message: string,\n ) {\n super(`[${reason}] ${message}`);\n this.name = 'MappingArtifactError';\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * → 每个模型声明的业务键字段清单(没声明的模型给一个空数组,**不是**跳过)。\n *\n * 🔴 「这个模型没声明业务键」与「工件里根本没有这个模型」是两件事,\n * 上游按数组长度对得上模型条数才说得清是哪一件。\n */\nexport function readBusinessKeyFields(file: string): string[][] {\n let text: string;\n try {\n text = readFileSync(file, 'utf8');\n } catch (e) {\n const code = (e as NodeJS.ErrnoException)?.code;\n if (code === 'ENOENT') throw new MappingArtifactError('not-found', `找不到映射工件 ${file}`);\n throw new MappingArtifactError('unreadable', `${file} 读不动(${String(e)})`);\n }\n // BOM:Windows 记事本存出来的**合法**工件常带它,按 utf-8 直解会以\n // 「第 1 行第 1 列」被拒,而报错完全指不到真因。\n if (text.charCodeAt(0) === 0xfeff) text = text.slice(1);\n\n let body: unknown;\n try {\n body = JSON.parse(text);\n } catch (e) {\n throw new MappingArtifactError('not-json', `${file} 不是合法 JSON:${String(e)}`);\n }\n if (!isRecord(body)) {\n throw new MappingArtifactError('bad-shape', `${file} 的根不是对象`);\n }\n if (!('version' in body)) {\n // 「没写版本」与「写了个不认识的版本」不是一回事,报错也不许同形。\n throw new MappingArtifactError('version-missing', `${file} 没有 version 字段`);\n }\n if (body.version !== MAPPING_ARTIFACT_VERSION) {\n throw new MappingArtifactError(\n 'unsupported-version',\n `${file} 的 version 是 ${JSON.stringify(body.version)},本工具只认 ${MAPPING_ARTIFACT_VERSION}` +\n ' ——「尽量按老版本读」会把新语义静默丢掉',\n );\n }\n const models = body.models;\n if (!Array.isArray(models)) {\n throw new MappingArtifactError(\n 'bad-shape',\n `${file} 的 models 应当是一个数组,实际是 ${models === null ? 'null' : typeof models}`,\n );\n }\n if (models.length === 0) {\n throw new MappingArtifactError(\n 'empty',\n `${file} 的 models 是空的 —— 空工件跑完什么也不会发生,而顾问会以为「导过了」`,\n );\n }\n return models.map((raw, i) => {\n if (!isRecord(raw)) {\n throw new MappingArtifactError('bad-shape', `${file} 的 models[${i}] 不是对象`);\n }\n const businessKey = raw.businessKey;\n if (businessKey === undefined || businessKey === null) return [];\n if (!isRecord(businessKey)) {\n throw new MappingArtifactError('bad-shape', `${file} 的 models[${i}].businessKey 不是对象`);\n }\n const fields = businessKey.fields;\n if (fields === undefined || fields === null) return [];\n if (!Array.isArray(fields)) {\n throw new MappingArtifactError(\n 'bad-shape',\n `${file} 的 models[${i}].businessKey.fields 不是数组`,\n );\n }\n return fields.filter((f): f is string => typeof f === 'string' && f !== '');\n });\n}\n","/**\n * 门 0 · B 类「版本级协议事实」登记册 —— 判红公式里**唯一的豁免通道**(`P`)。\n *\n * ## 加一条之前先过两问(判据逐字沿用 kit 的 `protocol-facts.yml` 表头)\n *\n * ① recon 查得到吗?查得到 = **A 类环境事实**,不写进 skill、也不登记在这里\n * (登记了照样判红 —— 通道三 `P ∩ A`)。\n * ② 查不到,但随**产品版本**变吗?变 = B 类,登记在这里,**并挂一个断行为的探针**。\n *\n * 🔴 **没有探针的条目不给任何豁免**(不进 `P`),还多一条台账(通道四判红)。\n * 「每个条目要么进 P、要么上账本,没有第三种下场」是这一层的不变式。\n *\n * ## 🔴 与 kit 的三处**故意不同**,逐条写在这里(ADR-319 刀 8)\n *\n * 1. **登记册是随包的常量,不再是一份 YAML**。kit 那边为读它自造了一个迷你 YAML\n * 解析器(`gate0/minyaml.py`,412 行)—— 行为契约清单 §5.2 已裁定那一族整体\n * **不移植**(G 25 + X 3):TS 有原生 JSON/YAML,而**解析器本身不是能力**。\n * 2. **没有 `--facts` 这个入口**。现场顾问登记不了一条 —— 登记要附一个断行为的探针,\n * 而探针跑在这个仓的 CI 里。⇒ 通道二的判词说的出路也跟着改口(见 `scan.ts`):\n * 「改写成不依赖它的说法」或「给 CLI 提一条登记」,**不是**「往某个 yml 里加一行」。\n * 指一条现场走不通的出路,与指一条敲不通的命令同罪。\n * 3. **探针形态是 `<相对本仓的测试文件>::<`// P:` 标记>`**。kit 那边是\n * `<file>.py::<pytest 函数名>`,靠 AST 找模块级函数;TS 的用例是 `it('中文标题')`,\n * 没有可寻址的函数名 ⇒ 改用一条显式标记。**它与 `// C:` 是两回事**:\n * `// C:` 说的是「这条用例兑现了契约清单的哪一条」,`// P:` 说的是\n * 「这条用例给登记册里的哪条事实作背书」。\n *\n * ## 三档拒收,各自的成因分得开\n *\n * · `probe-shape` —— 探针不是 `<文件>::<名字>` 形态。**只给文件路径 = 同义反复**\n * (「那个文件存在」不算背书),当场拒收。\n * · `probe-absent` —— 形态对,但那个文件里找不到这条 `// P:` 标记 ⇒ 背书是空的。\n * · `duplicate-token` —— 同一个 token 登记了两遍,**两边都扔**:留下任何一边都要\n * 回答「以哪一份为准」,而那个问题没有正确答案。\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport * as path from 'node:path';\n\n/** 一条登记项。三个键**恰好**是这些——多一个键不是「宽容」,是没人知道它什么意思。 */\nexport interface ProtocolFact {\n /** 协议形态本身:端点写模板形(`/api/models/{model}/meta`),错误码写字面。 */\n readonly token: string;\n /** `<相对仓根的测试文件>::<该文件里的 `// P:` 标记>`。 */\n readonly probe: string;\n /** 这条事实说的是什么;给人读,不参与判据。 */\n readonly note: string;\n}\n\n/** 台账作用域:整册级 vs 单条级。两者的下游去处不同(见 `scan.ts`)。 */\nexport const REGISTRY_SCOPE = 'registry';\nexport const ENTRY_SCOPE = 'entry';\nexport const FACTS_SCOPES = [REGISTRY_SCOPE, ENTRY_SCOPE] as const;\nexport type FactsScope = (typeof FACTS_SCOPES)[number];\n\n/** 整册级成因。 */\nexport const REASON_NO_ENTRIES = 'no-entries';\n/** 没给探针根 ⇒ 一条探针都没验过(**不是**「都验过了」)。 */\nexport const REASON_PROBE_UNVERIFIED = 'probe-unverified';\nexport const REGISTRY_REASONS = [REASON_NO_ENTRIES, REASON_PROBE_UNVERIFIED] as const;\n\n/** 单条级成因。 */\nexport const REASON_PROBE_SHAPE = 'probe-shape';\nexport const REASON_PROBE_ABSENT = 'probe-absent';\nexport const REASON_DUPLICATE_TOKEN = 'duplicate-token';\nexport const ENTRY_REASONS = [\n REASON_PROBE_SHAPE,\n REASON_PROBE_ABSENT,\n REASON_DUPLICATE_TOKEN,\n] as const;\n\nexport const FACTS_REASONS = [...REGISTRY_REASONS, ...ENTRY_REASONS] as const;\nexport type FactsReason = (typeof FACTS_REASONS)[number];\n\n/** 一条台账。`token` 在整册级那一档是 `null`(那时候还没有具体条目可点名)。 */\nexport interface FactsProblem {\n readonly scope: FactsScope;\n readonly reason: FactsReason;\n /** 第几条(从 1 起);整册级是 0。 */\n readonly index: number;\n readonly token: string | null;\n readonly message: string;\n}\n\n/**\n * 探针形态:`<路径>.test.ts::<标记>`。\n *\n * 🔴 **`::` 那一半不是可选的** —— 只给文件路径是同义反复:「那个文件存在」不构成\n * 「这条事实被断言过」。形态判据在这里,不在调用方。\n */\nconst PROBE_SHAPE = /^(?<file>[A-Za-z0-9_./-]+\\.test\\.ts)::(?<mark>[A-Za-z0-9_-]+)$/;\n\n/** 探针标记在测试文件里的写法。**与 `// C:` 是两套记号**,理由见模块 docstring。 */\nexport const PROBE_MARK_PREFIX = '// P:';\n\n/**\n * 随包登记册。\n *\n * 🔴 **两条,不是三条** —— kit 那边还登记着 `/api/auth/login`,这里**没有**,\n * 而这是按 kit 自己那条纪律作的决定:**写不出断行为的探针就不登记**。\n * 行为契约清单 §1.1 把「登录请求体带 tenantId、header 不带」判给了 CLI 既有的认证管道\n * (`packages/core/src/auth/password.ts`),那一半**没有**任何一条用例断它的 wire shape\n * ——「已核实」是一句读代码得来的话,不是一条会红的判据。\n * ⇒ 本刀补了一条真断它的用例(`fde-gate0-facts.test.ts` 里那条带 `// P:` 标记的),\n * 于是这条事实**付得起**探针,登记成立。**先有探针,才有登记**,顺序不许反。\n */\nexport const PROTOCOL_FACTS: readonly ProtocolFact[] = [\n {\n token: '/api/models/{model}/meta',\n probe: 'packages/core/tests/fde-recon-crawl.test.ts::meta-path-wire-shape',\n note:\n '字段元数据走裸路径 GET,默认不缀 ?type=。' +\n '探针断的是**实际发出去的 URL**,不是拼串函数的返回值。',\n },\n {\n token: '/api/models/{model}/list/dsl',\n probe: 'packages/core/tests/fde-recon-valuedomain.test.ts::list-dsl-count-probe',\n note:\n '列表查询走 POST DSL。探针断的是行为:size=0 的那一发先出且返回 totalElements,' +\n '取页判据以此为前提。',\n },\n {\n token: '/api/auth/login',\n probe: 'packages/core/tests/fde-gate0-facts.test.ts::login-body-carries-tenant',\n note:\n '登录的 tenantId 在**请求体**里,不在 header。' +\n '探针断的是实际发出去的请求体与请求头,删掉 tenantId 或挪进 header 都会红。',\n },\n];\n\nexport interface LoadedFacts {\n /** 进了 `P` 的 token(付过探针的那些)。 */\n facts: string[];\n problems: FactsProblem[];\n}\n\n/**\n * 装载登记册 → `(P, 台账)`。\n *\n * ## 🔴 探针在**什么时候**被验,与 kit 不同 —— 这一处必须说准\n *\n * kit 侧探针是**运行时**验的:它的包里带着自己的 `tests/`,现场跑一次就能确认每条登记\n * 都真有背书。**npm 包里没有测试**(`files` 只发 `dist/**`)⇒ 同一个做法搬过来的后果是\n * 每一次现场调用都判「一条探针都没验过」⇒ **rc 恒为 2**,闸从此说不出任何话。\n *\n * ⇒ 验的时刻**搬到构建期**:`PROTOCOL_FACTS` 是编译进包的常量,本仓的\n * `fde-gate0-facts.test.ts` 逐条把它的探针解析到真文件真标记 —— 那条用例红,包就发不出去。\n * 于是随包登记册在运行时**已经**是验过的,不必也无法再验一次。\n *\n * 🔴 **但「没查」与「查过」仍然不许同形**,所以这条豁免**只给随包那一份**:\n * · 传进来的就是 `PROTOCOL_FACTS`(同一个引用)且没给 `probeRoot` ⇒ 构建期那道闸背书,不记账;\n * · 传的是**别的**数组且没给 `probeRoot` ⇒ **没有任何东西验过它** ⇒ 记一条整册级\n * `probe-unverified`(下游据此判前提不成立)。\n * · 给了 `probeRoot` ⇒ 当场逐条解析(本仓那条用例走的正是这条路)。\n */\nexport function loadProtocolFacts(\n entries: readonly ProtocolFact[] = PROTOCOL_FACTS,\n options: { probeRoot?: string } = {},\n): LoadedFacts {\n const problems: FactsProblem[] = [];\n const probeRoot = options.probeRoot;\n if (probeRoot === undefined && entries !== PROTOCOL_FACTS) {\n problems.push({\n scope: REGISTRY_SCOPE,\n reason: REASON_PROBE_UNVERIFIED,\n index: 0,\n token: null,\n message:\n '这份登记册不是随包那一份,又没有给探针根目录 ⇒ 一条探针都没验过 ——' +\n '「探针都在」与「没查探针」在返回值上同形,所以这里记一笔,不装作验过',\n });\n }\n if (entries.length === 0) {\n problems.push({\n scope: REGISTRY_SCOPE,\n reason: REASON_NO_ENTRIES,\n index: 0,\n token: null,\n message: '登记册一条都没有 —— **查过且为空**,不是「没查」',\n });\n }\n\n // 先按 token 数一遍:重复的**两边都扔**(留哪一边都要回答「以哪份为准」)。\n const times = new Map<string, number>();\n for (const entry of entries) times.set(entry.token, (times.get(entry.token) ?? 0) + 1);\n\n const facts: string[] = [];\n entries.forEach((entry, position) => {\n const index = position + 1;\n if ((times.get(entry.token) ?? 0) > 1) {\n problems.push({\n scope: ENTRY_SCOPE,\n reason: REASON_DUPLICATE_TOKEN,\n index,\n token: entry.token,\n message: `${entry.token} 登记了不止一次 ⇒ 两条都不进 P(留下任何一边都要回答「以哪份为准」)`,\n });\n return;\n }\n const shape = PROBE_SHAPE.exec(entry.probe);\n if (!shape?.groups) {\n problems.push({\n scope: ENTRY_SCOPE,\n reason: REASON_PROBE_SHAPE,\n index,\n token: entry.token,\n message:\n `${entry.token} 的探针 ${JSON.stringify(entry.probe)} 不是 ` +\n '`<测试文件>.test.ts::<标记>` 形态 —— 只给文件路径是同义反复' +\n '(「那个文件存在」不算背书)',\n });\n return;\n }\n if (probeRoot !== undefined) {\n const file = path.join(probeRoot, shape.groups.file as string);\n const mark = `${PROBE_MARK_PREFIX} ${shape.groups.mark}`;\n const found = existsSync(file) && readFileSync(file, 'utf8').includes(mark);\n if (!found) {\n problems.push({\n scope: ENTRY_SCOPE,\n reason: REASON_PROBE_ABSENT,\n index,\n token: entry.token,\n message:\n `${entry.token} 的探针指向 ${entry.probe},但那里找不到 ` +\n `\\`${mark}\\` 这条标记 ⇒ 这条登记没有背书,不进 P`,\n });\n return;\n }\n }\n facts.push(entry.token);\n });\n\n return { facts, problems };\n}\n","/**\n * 门 0 · 五通道成稿闸 —— 把 A 类(环境事实)与 P(版本级协议登记册)合起来判。\n *\n * ## 判红公式(**四条并联,不是减法**)\n *\n * A = fieldKey ∪ fieldLabel ∪ enumValue ∪ modelName ∪ codeValue ∪ naturalKey ∪ caliber\n * P = 登记册中「已登记 且 带可执行探针」的条目\n *\n * 判红 ⟺ token ∈ A # 通道一:环境事实,**无豁免**\n * ∨ (regexHit(endpoint|errorCode) ∧ token ∉ P) # 通道二:协议形态,登记可豁免\n * ∨ 存在登记项 ∈ P ∩ A # 通道三:登记错了类\n * ∨ 存在登记项被拒 # 通道四\n *\n * 🔴 **通道一无豁免**:A 类不可能同时是版本级协议(recon 查得到 = A 类,查不到才可能是\n * B 类)。写成「A 减去 P」会与通道三**自相矛盾** —— 通道三说「登记了 A 类要判红」,\n * 减法说「登记了 A 类就放行」,同一份登记册在两条通道上得出相反结论。\n *\n * ## `channels` 有**五个**键,公式只有四条通道 —— 差在哪\n *\n * 第五个是 `artifact-path`:**产物路径片段无豁免**。它与通道一同族(都是环境事实、\n * 都不查 `P`),但它的 token **不在 `A` 里** —— `A` 是从产物**内容**派生的,产物**路径**\n * 不在其中。塞进 `aclass` 那一格会让「token ∈ A」这句话变成假话。\n *\n * ## 三档退出码(`exitCode`),**不许合并**\n *\n * 0 无判红\n * 1 有判红(逐条点名通道与 token)\n * 2 **前提不成立** —— 产物缺失 / 某个 A 类子集为空 / A 类少了一部分 /\n * 登记册没立住 / 扫描面为空 / 有文件读不动 / 基线不是一轮跑出来的 / 有软链没扫\n *\n * 🔴 rc=2 **压过** rc=1:前提不成立时那份判红清单本身不可信(少了一整类 A,它的绿是假绿)。\n * 判红清单照打 —— rc 是给脚本的闸,清单是给人的依据。\n * 🔴 **「查过且为空」与「根本没查」在 rc 上必须分得开**:登记册一条都没有(查过、确实\n * 一条都没登记)**不是**前提级,端点提及照常按通道二判红(rc=1);登记册这一层**没立住**\n * 才是前提级(rc=2)。取值域见 `PREMISE_REGISTRY_REASONS`。\n *\n * ## token 口径与它**声明的缺口**\n *\n * 匹配单位是 **token(按分词边界)**,不是子串:`hireDate` 出现在 `myhireDates` 里\n * **不判红**。边界只在**词字符**(`[A-Za-z0-9_]`)那一侧成立 ——\n *\n * · 🔴 **缺口①:纯数字 / 单字符 token 不判**(`inGap`)。码表行里的 `7`、枚举 label 里的\n * `男` 都是**真的** A 类取值,但拿它们判红会把任何一份中文文档淹掉。它们**查过**,\n * 只是按缺口放行 —— 逐条落在 `gaps` 里,**不是**「没查」。\n * · 🔴 **缺口②:CJK 侧没有分词边界 ⇒ 这一档分桶,不分对错**。两侧都不是汉字 ⇒ 照常判红;\n * **任一侧是汉字**(`数字员工` 里的 `员工`)⇒ 进 `gaps` 的 `cjk-inside-longer-run`\n * 那一桶,**不顶 rc,但逐条印出**。\n * 🔴 **这一档是有代价的,不是「两头都不丢」**:中文行文里 A 类取值紧邻的几乎必然是\n * 汉字(`的` `和` `与`),于是**无歧义的真命中也被分进这一桶** —— 看得见但不拦人。\n * ⇒ 对 A 类里中文形态那一半,`rc=0` **不等于**它干净,`[gaps]` 段得人读一遍。\n * ⚠️ 中文标点(`「」`、`,`)**不算汉字** —— 它们是真边界,两侧是标点时照常判红。\n * · 🔴 **缺口③:端点正则只认 `/api/` 与 `/ws/` 起头的形态**,且必须**从左边界起**。\n * 别的形态的端点**逮不住**(欠判)。放宽成「任意绝对路径」会把 `/etc/passwd` 判成\n * 未登记端点,那条消息会把人指向登记册。\n * · 🔴 **缺口⑤:URL 里的 host 不判**(`http://localhost:8080/api/…` 的 `localhost:8080`)。\n * 它**是**环境事实,但不在 `A` 里,也不属于四条通道里的任何一条 ⇒ **欠判,至今不修**。\n * ⚠️ 别把「端点判得出来」读成「这行 URL 干净了」。\n * · 🔴 **缺口⑥:外部产品文档的 URL 会被抽成端点**。\n * `https://code.visualstudio.com/api/references/contribution-points` 这一类链接逐字符合\n * 端点形态 ⇒ 通道二会对它判一条**假红**。收窄要么按 host 白名单(那是一张没人维护的\n * 登记表),要么认「本产品的 host」(门 0 是零网络的编写期闸,它不知道)。**至今不修**。\n * ⚠️ 别把它塞进反例语料去「证明不过判」—— 那是把已知缺陷钉成期望。\n * · 🔴 **缺口⑦:任何 SCREAMING_SNAKE 标识符都会被通道二判成「未登记错误码」**。\n * 环境变量名、常量名、枚举名逐字符长这样。⇒ **方向是只出假红、不出假绿**:\n * 它多判,不漏判 —— 一个真错误码不会因此逃掉。\n * · 🔴 **缺口⑧:软链目录不跟随**。递归**不进**符号链接目录 ⇒ 门 0 没顺着那条链走过一个字。\n * **不跟随是刻意的**(跟过去要处理环路与越界,链到根就把整盘扫了),但**当场声明**:\n * 每条没被扫到的软链条目记一条前提级台账(rc=2)。指向**文件**的软链照旧扫。\n * ⇒ **残留欠判**:链后面那份正文一个字都没判过,本闸买的是**不静默**,不是覆盖。\n * · 🔴 **缺口⑨:同形不同码 —— 修了两类,别的写法明确没修**。\n * · **不可见的格式控制字符**(`hireDate` 中间夹一个零宽字符)⇒ **已修**:判前删掉\n * Unicode `Cf` **全族**(判据是 `\\p{Cf}`,不是手写码位表 —— 手写名单漏登永远不红)。\n * · **全角字母数字**(`hireDate`)⇒ **已修**:三段 1:1 映射回 ASCII。\n * ⚠️ **只映射字母与数字,不动标点**,理由钉在 `FULLWIDTH_ALNUM_RANGES` 上方。\n * · **大小写变形仍不判**(`HIREDATE`)⇒ **明确没修,不是漏了**:A 类里有 `code` 这一类\n * 通用词,casefold 之后英文散文里 `Claude Code` 这类写法会整片判红。\n * · **西里尔同形字与数学字母仍不判**(`hirеDate` 里那个 `е` 是 U+0435)⇒ **明确没修**。\n * ⚠️ 上面这份清单**不是「同形不同码」这一类的穷举**。\n * 🔴 **归一只加不减**:**原文判一次、归一后判一次,红取并集**(见 `judgeLine`)。\n * 只判归一后的行时,同一个「把字接起来」的机制会在**两个方向**上生效,而只有\n * 「松 → 紧」那一个方向被论证过 —— 实测两条真红会因此降级(一条降成缺口②、\n * 一条因词边界翻转直接消失)。门 0 是**阻断闸**,`rc=1 → rc=0` 的位移必须是有人设计过的。\n * 🔴 **第二样没修的**:归一只作用在**被扫的那一行**,**不作用在 A 类 token 自己**。\n * 取值本身带不可见字符 / 全角时,归一后的那一趟对不上它 ⇒ 那一档照旧漏判。\n * ⚠️ **判词得自己交代**:归一之后才认出来的红 / 缺口,末尾带 `FOLDED_NOTE` 并**交出\n * 原文里那一段的字面** —— 不给它,顾问拿判词里那个词去原文里 grep 会一无所获,\n * 然后判定门 0 报了假红。贴不贴的条件是「**这个 token 在原文里找不到**」,\n * 不是「这一行被归一改动过」:带 BOM 的文件整行都被改动过,可 token 本身 grep 得到。\n * · 🔴 **缺口④:登记项内部嵌着 A 类 token 时(`/api/models/Employee/meta`)通道三判不\n * 出来** —— 通道三是**集合交**(`P ∩ A`),登记项是整条比对的。这个洞很窄:正文里那个\n * `Employee` 仍会被通道一判红(无豁免),夹带进登记册买不到任何豁免。\n *\n * ## 🔴 匹配有两条实现路径,判据必须**逐字相同**\n *\n * 纯词形 token(`^[A-Za-z0-9_]+$`)走「按词切出来的集合」查表(O(1));其余\n * (`code+orgUnitCode` / `019f-0001` / 中文 / 带空格的 `United States`)走逐行 find +\n * 边界判定。两条路等价**不是显然的**,由用例现算着钉住。\n */\nimport { readFileSync, readdirSync, readlinkSync, statSync } from 'node:fs';\nimport * as path from 'node:path';\n\nimport {\n ACLASS_KINDS,\n CLASS_REASONS,\n CLASS_SCOPE,\n ENTRY_SCOPE,\n REASON_EMPTY as ACLASS_REASON_EMPTY,\n REASON_UNREAD as ACLASS_REASON_UNREAD,\n type Aclass,\n type AclassKind,\n type AclassProblem,\n} from './aclass';\nimport { REASON_NO_ENTRIES, REGISTRY_REASONS, REGISTRY_SCOPE, type FactsProblem } from './facts';\nimport type { RoundResult } from '../recon/runner';\n\n// ── 通道名 ────────────────────────────────────────────────────────────────────\nexport const CHANNEL_ACLASS = 'aclass';\nexport const CHANNEL_ARTIFACT = 'artifact-path';\nexport const CHANNEL_UNREGISTERED = 'protocol-unregistered';\nexport const CHANNEL_REGISTERED_IS_ACLASS = 'registered-but-is-aclass';\nexport const CHANNEL_NO_PROBE = 'no-probe';\nexport const CHANNELS = [\n CHANNEL_ACLASS,\n CHANNEL_ARTIFACT,\n CHANNEL_UNREGISTERED,\n CHANNEL_REGISTERED_IS_ACLASS,\n CHANNEL_NO_PROBE,\n] as const;\nexport type Channel = (typeof CHANNELS)[number];\n// 🔴 **只有通道二查 `P`**。这条不变式由**行为**守着,不由某个常量守着:\n// 把通道一改成查 `P`、把产物路径改成查 `P`,两刀都当场红。\n\n// ── 前提级台账的取值域 ────────────────────────────────────────────────────────\nexport const REASON_NO_SKILL_ROOT = 'no-skill-root'; // 扫描面根本不在盘上\nexport const REASON_NOTHING_SCANNED = 'nothing-scanned'; // 根在,一个文件都没有 —— 查过且为空\nexport const REASON_FILE_UNREADABLE = 'file-unreadable'; // 某个文件读不动 ⇒ 判不了它干不干净\nexport const REASON_ACLASS_EMPTY = 'aclass-empty'; // 某个 A 类子集为空 ⇒ 那一类恒绿\n/**\n * **第三档「查了、只查到一部分」**:这一类**有人守,但少了一部分**。\n * 「查过且为空」与「根本没查」此前分得开,唯独这一档在 `scan` 这一跳被压平成\n * 「查过、干净」—— `deriveAclass` 明明逐条记了条目级台账(坏行 / 丢条目 / 丢键 /\n * 坏码表行),下游一条都不取,于是那一行数据里的环境事实**逐字写进 skill 也判不出来**。\n */\nexport const REASON_ACLASS_PARTIALLY_UNREAD = 'aclass-partially-unread';\nexport const REASON_REGISTRY = 'registry-not-established'; // 登记册这一层没立住(P 不可信)\nexport const REASON_CHANNEL_FOUR_UNCHECKED = 'channel-four-unchecked'; // 没把登记册台账传进来\n/**\n * **基线不是一轮完整跑出来的**:A 类事实是从这份基线**现算**的,基线混着不同轮次\n * (`mixed`)或压根没有轮次身份(`unknown`)时,那个集合本身就混轮次 / 判不了\n * ⇒ 「判红 0 条」对**哪一轮都不算数**。\n */\nexport const REASON_BASELINE_ROUND_NOT_COMPLETE = 'baseline-round-not-complete';\n/** 软链目录 / 环路软链 / 断链:**没扫到**不许与「扫过、干净」同形。 */\nexport const REASON_SYMLINK_NOT_SCANNED = 'symlink-not-scanned';\nexport const PREMISE_REASONS = [\n REASON_NO_SKILL_ROOT,\n REASON_NOTHING_SCANNED,\n REASON_FILE_UNREADABLE,\n REASON_ACLASS_EMPTY,\n REASON_ACLASS_PARTIALLY_UNREAD,\n REASON_REGISTRY,\n REASON_CHANNEL_FOUR_UNCHECKED,\n REASON_BASELINE_ROUND_NOT_COMPLETE,\n REASON_SYMLINK_NOT_SCANNED,\n] as const;\nexport type PremiseReason = (typeof PREMISE_REASONS)[number];\n\n/**\n * **哪些整册级台账算前提不成立**:除了「查过且为空」之外**全部**。\n *\n * 🔴 写成「全集减一个」而不是逐个枚举 —— 逐个枚举的那张表与 `REGISTRY_REASONS` 之间\n * 没有任何东西绑住,对面加一档时这里静默漏掉,而漏掉的后果是「登记册根本没立住」\n * 被判成 rc≤1,顾问照着一份**没有豁免通道**的判红清单去改 skill。\n */\nexport const PREMISE_REGISTRY_REASONS: readonly string[] = REGISTRY_REASONS.filter(\n (r) => r !== REASON_NO_ENTRIES,\n);\n\n// ── 扫描面与形态 ──────────────────────────────────────────────────────────────\n/** 词字符:分词边界只在这一侧成立(CJK 侧没有边界,见缺口②)。 */\nconst WORD_RE = /[A-Za-z0-9_]+/g;\nconst SIMPLE_TOKEN = /^[A-Za-z0-9_]+$/;\nconst WORD_CHARS = new Set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_');\n/** 「这个位置是不是词字符」—— 越界(`undefined`)不是词字符,与「行首/行尾」同义。 */\nfunction isWordChar(char: string | undefined): boolean {\n return char !== undefined && WORD_CHARS.has(char);\n}\n\n/**\n * 端点:只认 `/api/` 与 `/ws/` 起头(缺口③)。`{model}` 这种模板段要收得下 ——\n * 登记册登记的是**模板形态**,skill 正文里出现的往往是**具体形态**,两者必须能对上\n * (对不上 ⇒ `P` 恒不命中 ⇒ 每一处端点提及都判假红,而两边都全绿)。\n *\n * 🔴 **左边界锚点不是装饰**:没有它时 `docs/reference/api/model-api-quickref.md` 会被抽成\n * `/api/model-api-quickref.md` —— 一条**文档路径**被判进**可豁免的通道二**,消息叫人\n * 「登记一条并挂上断行为的探针」。那比单纯指错方向更远:它**诱导往登记册里塞非协议条目**,\n * 而登记册是整条公式里唯一的豁免通道,污染它等于给别的东西开后门。\n * 🔴 放宽到只排除字母数字**会重新打开这个缺陷**(多抽以 `.md` 收尾的文档路径),\n * 别顺手去调排除集。\n */\n// KNOB: endpoint-prefixes —— 只认这两个前缀(放宽成「任意绝对路径」会把 /etc/passwd 判成端点)\nexport const ENDPOINT_PREFIXES = ['api', 'ws'] as const;\nconst ENDPOINT_PATH = `/(?:${ENDPOINT_PREFIXES.join('|')})(?:/(?:\\\\{[A-Za-z0-9_]+\\\\}|[A-Za-z0-9_.\\\\-]+))+`;\n/** 裸路径形态:左边不许是路径字符。 */\n// KNOB: bare-endpoint-left-anchor —— 左边不许是路径字符(放宽到裸字母数字会重开文档路径那条假红)\nconst BARE_ENDPOINT_RE = new RegExp(`(?<![A-Za-z0-9_.\\\\-/])${ENDPOINT_PATH}`, 'g');\n/**\n * **URL 形态**:左锚点把 `http://host/api/…` 这一整类挡在外面 —— 错因是 **host 的末位\n * 字母数字**(`http://localhost:8080/api/…` 里 `/api` 前面那个字符是 `0`,任何形态的\n * 左锚点都拦它),不是排除集里的 `/`。⇒ 治法只能是**另开一支专门认 URL**。\n * ⚠️ token 取的是**路径部分**(剥掉 scheme + host):登记册登的是路径,带上 host 就永远\n * 对不上 `P`,而 host 本身逐环境不同(见缺口⑤)。\n * 🔴 host 与端点之间**允许还有前缀段**(`http://h/x/api/y`):非贪婪、不含空白即可。\n */\nconst URL_ENDPOINT_RE = new RegExp(\n `[A-Za-z][A-Za-z0-9+.\\\\-]*://[^\\\\s\"'\\`<>]*?(${ENDPOINT_PATH})`,\n 'g',\n);\n/**\n * **scheme-less 形态**:`localhost:8080/api/…`。它既不带 `scheme://`,左边又紧挨着 host\n * 的字母数字 ⇒ 两支都不认。\n * ⚠️ 这一支**必须比 URL 那一支窄**:host 后面**不许再有前缀段**,端点要**紧挨着** host。\n * 放宽的话 `docs/reference/api/x.md` 里的 `quickref.md` 会被当成 host。\n */\n// KNOB: hostless-host-shape —— host 只认「带点的域名」或「名字:端口」(放宽会把 quickref.md 当成 host)\nconst HOSTLESS_HOST = `(?:(?:[A-Za-z0-9_\\\\-]+\\\\.)+[A-Za-z]{2,}(?::[0-9]+)?|[A-Za-z0-9_\\\\-]+:[0-9]+)`;\nconst HOSTLESS_ENDPOINT_RE = new RegExp(\n `(?<![A-Za-z0-9_.\\\\-/:@])${HOSTLESS_HOST}(${ENDPOINT_PATH})`,\n 'g',\n);\n/** 错误码:`BR_INVALID_ENUM_VALUE` 这一档。至少两段,全大写 + 下划线。 */\nconst ERROR_CODE_RE = /(?<![A-Za-z0-9_])[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+(?![A-Za-z0-9_])/g;\n/** 相对路径片段 / 裸产物文件名。 */\nconst PATHISH_RE = /(?<![A-Za-z0-9_./-])\\.?[A-Za-z0-9_.-]+(?:\\/[A-Za-z0-9_.-]+)+/g;\n/**\n * 产物文件名的后缀集。🔴 **只有这一个载体**:把 `json` / `jsonl` 在正则里再硬编码一遍时,\n * 「哪些后缀算产物」会**分成两个答案**,症状是某一档静默不再命中 —— 而且往其中一个载体\n * 打的变异**根本不生效**,差点被读成「守卫有洞」。\n */\n// KNOB: artifact-suffixes —— 产物文件名后缀集(加一档 .md 会把每一份技能正文判红)\nexport const ARTIFACT_SUFFIXES = ['.json', '.jsonl'] as const;\nconst PRODUCT_FILE_RE = new RegExp(\n `(?<![A-Za-z0-9_./\\\\-])[A-Za-z0-9_.\\\\-]+(?:${ARTIFACT_SUFFIXES.map((s) => s.replace('.', '\\\\.')).join('|')})(?![A-Za-z0-9_])`,\n 'g',\n);\nconst PLACEHOLDER_RE = /\\{[A-Za-z0-9_]*\\}/g;\n\n/**\n * 产物路径两档,`product-tree-path` 那一档**从产物根锚定**。\n *\n * 起手写的是「路径里有一段叫 `recon`/`index`/`meta`/`mapping`/`valuedomain`/`work`」——\n * 那张目录名表**过判得离谱**:`src/index/router.ts`、`docs/meta/field-spec.md` 全被判成\n * 产物路径,消息同样把人指错方向。⇒ 要求**真的从产物根起**。\n */\n// KNOB: artifact-root —— 产物根锚点(换成一个常见目录名会让整棵源码树判红)\nexport const ARTIFACT_ROOT = '.hcmnx';\n// KNOB: recon-dirname —— 产物树第一段(与 ReconLayout 真拼出来的那一段绑住)\nexport const RECON_DIRNAME = 'recon';\n// KNOB: recon-segments-below-root —— 数值旋钮:调小会把 src/recon/x.ts 这类源码路径判红\nexport const RECON_SEGMENTS_BELOW_ROOT = 2; // <租户段>/<sourceKey 段>\n/**\n * 产物根**下面一层**的形状。收窄成「从产物根锚定」时 `index/models.md` 这份**真产物**\n * 掉出去了 ⇒ 补一档「**恰好两段**且(第一段, 末段后缀)是真产物真有的那几对」。\n * 🔴 **末段必须是产物后缀**:只判「两段 + 产物子目录」时,`meta/capabilities` /\n * `index/version` 这类**根本不是文件路径**的写法全被判红。\n * 🔴 判的是**(子目录, 后缀)这一对**,不是两个独立集合的乘积:乘积里有 `meta/*.md`\n * 这种真产物根本没有的组合,而散文里真有 `meta/README.md`。\n * ⚠️ 名单与**真跑一次 recon** 落盘的形状绑住(`==` 不是 `⊆`,现算):`⊆` 只管\n * 「真产物的形状都在表里」,不管表里有没有多余的对。\n */\n// KNOB: product-relative-shapes —— (子目录, 后缀)**对**(加回 ('meta', '.md') 会重开那条过判)\nexport const PRODUCT_RELATIVE_SHAPES: ReadonlyArray<readonly [string, string]> = [\n ['index', '.md'],\n ['index', '.jsonl'],\n ['mapping', '.json'],\n ['meta', '.json'],\n ['valuedomain', '.json'],\n ['valuedomain', '.jsonl'],\n];\n\n/**\n * 每条通道**认得的 reason**。同一条通道下的不同成因要分得开:产物路径那一条此前两档\n * 共用一句话,于是「从产物根抄下来的路径」与「一个恰好以 `.json` 收尾的文件名」在返回值上\n * 逐字同形 —— 而后者是**过判**,读的人无从判断该不该照着改。\n */\nexport const REASON_ENVIRONMENT_FACT = 'environment-fact';\nexport const REASON_PRODUCT_TREE_PATH = 'product-tree-path';\nexport const REASON_PRODUCT_RELATIVE_PATH = 'product-relative-path';\nexport const REASON_PRODUCT_FILE_NAME = 'product-file-name';\nexport const REASON_ENDPOINT = 'endpoint';\nexport const REASON_ERROR_CODE = 'error-code';\nexport const REASON_REGISTERED_IS_ACLASS = 'registered-is-aclass';\nexport const REASON_REJECTED_REGISTRATION = 'rejected-registration';\nexport const RED_REASONS: Record<Channel, readonly string[]> = {\n [CHANNEL_ACLASS]: [REASON_ENVIRONMENT_FACT],\n [CHANNEL_ARTIFACT]: [\n REASON_PRODUCT_TREE_PATH,\n REASON_PRODUCT_RELATIVE_PATH,\n REASON_PRODUCT_FILE_NAME,\n ],\n [CHANNEL_UNREGISTERED]: [REASON_ENDPOINT, REASON_ERROR_CODE],\n [CHANNEL_REGISTERED_IS_ACLASS]: [REASON_REGISTERED_IS_ACLASS],\n [CHANNEL_NO_PROBE]: [REASON_REJECTED_REGISTRATION],\n};\n\n/**\n * **每个 reason 一句自己的话,键集与 `RED_REASONS[CHANNEL_ARTIFACT]` 相等**。\n *\n * 起手这里是一句**二分**的三元表达式,而取值域早已是**三档** —— 中间没有任何东西绑住,\n * 于是新加的那一档落进 `else` 分支,对着 `index/models.md` 说「以 .json/.jsonl 收尾」\n * 「这一档会过判」:**两句全假**。后果比判错更难查 —— **判红是对的**,顾问会照着那两句\n * 去改,而那两句描述的是另一档。⇒ 查表少一档当场炸,不静默回落到别人的话。\n */\nexport const ARTIFACT_WORDING: Record<string, (token: string) => string> = {\n [REASON_PRODUCT_TREE_PATH]: (t) => `出现**产物树里的**路径 ${JSON.stringify(t)}`,\n [REASON_PRODUCT_RELATIVE_PATH]: (t) =>\n `出现**产物根相对路径** ${JSON.stringify(t)}(产物子目录下的那一份)`,\n [REASON_PRODUCT_FILE_NAME]: (t) =>\n `出现产物文件名形态 ${JSON.stringify(t)}(以 .json/.jsonl 收尾;**这一档会过判**,` +\n '它与产物无关时请照直说明)',\n};\n\n/**\n * 查表用它,**不许用 `?? '别的话'`**:少一档时当场炸,而不是静默回落到另一档的措辞\n * ——「判红是对的、说明是另一档的」比判错更难查。\n */\nfunction artifactWordingFor(reason: string): (token: string) => string {\n const wording = ARTIFACT_WORDING[reason];\n if (wording === undefined) {\n throw new Error(\n `产物路径成因 ${JSON.stringify(reason)} 没有对应的措辞` +\n `(认得的是 ${Object.keys(ARTIFACT_WORDING).join('/')})`,\n );\n }\n return wording;\n}\n\n/**\n * 每一档类级成因一句自己的话,键集与 `aclass.CLASS_REASONS` 相等。\n * 起手这两句话是**内联的字面量**,与 `aclass.ts` 的两个常量各写各的,中间没有任何东西\n * 绑住 —— 只改其中一个值,全套照样全绿,而后果是前提消息里「为什么这一类是空的」那半句\n * 静默消失,顾问只知道「空了」不知道该去查哪一头。\n */\nexport const ACLASS_EMPTY_WHY: Record<string, string> = {\n [ACLASS_REASON_UNREAD]: '(产物/工件那一头根本没读到)',\n [ACLASS_REASON_EMPTY]: '(产物读到了,一条取值都没派生出来)',\n};\n\n/**\n * 一条判红。\n *\n * 🔴 判红是**结构**不是字符串:通道、成因、token、文件、行号各占一格。分档信息一旦只活在\n * 中文消息里,判据就得去解析散文,改一个字悄悄失效。\n * `path` / `line` 在整册级的通道三、通道四上是 `null` / `0`。\n */\nexport interface Red {\n readonly channel: Channel;\n readonly reason: string;\n readonly token: string;\n readonly path: string | null;\n readonly line: number;\n readonly kinds: readonly AclassKind[];\n readonly message: string;\n}\n\n/** 一条**前提级**台账 —— 它让 rc 变成 2,而不是让判红清单多一条。 */\nexport interface Premise {\n readonly reason: PremiseReason;\n readonly message: string;\n}\n\n/** 缺口的两档。**分桶不是分对错**:说不准的那一档**看得见但不拦人**。 */\nexport const GAP_NOISE_TOKEN = 'pure-digit-or-single-char'; // 缺口①\nexport const GAP_CJK_INSIDE_LONGER_RUN = 'cjk-inside-longer-run'; // 缺口②\nexport const GAP_REASONS = [GAP_NOISE_TOKEN, GAP_CJK_INSIDE_LONGER_RUN] as const;\nexport type GapReason = (typeof GAP_REASONS)[number];\n\n/**\n * 一条**查过、按缺口放行**的记录。\n *\n * 🔴 没有它,「这个 token 落在缺口里」与「压根没找过这个 token」在返回值上逐字同形。\n */\nexport interface Gap {\n readonly reason: GapReason;\n readonly token: string;\n readonly kinds: readonly AclassKind[];\n readonly path: string;\n readonly line: number;\n readonly why: string;\n}\n\n/**\n * → 判红清单原样返回;通道名 / 成因认不出来就**抛**。\n *\n * 🔴 fail-loud:`'aclas'` 这种手误会让那一条从任何按通道分类的统计里静默消失,\n * 而「这一条判红了」与「这一条没判红」在按通道统计的结果上逐字同形。\n */\nexport function checkReds(reds: readonly Red[]): Red[] {\n for (const red of reds) {\n if (!(CHANNELS as readonly string[]).includes(red.channel)) {\n throw new Error(\n `判红的 channel 只有 ${CHANNELS.join('/')} 这几个取值,拿到的是 ` +\n `${JSON.stringify(red.channel)}(${red.message})`,\n );\n }\n if (!RED_REASONS[red.channel].includes(red.reason)) {\n throw new Error(\n `${JSON.stringify(red.reason)} 这个 reason 不属于 ${red.channel} 这条通道` +\n `(认得的是 ${RED_REASONS[red.channel].join('/')};${red.message})`,\n );\n }\n }\n return [...reds];\n}\n\n/** → 缺口台账原样返回;`reason` 认不出来就**抛**(理由同 `checkReds`)。 */\nexport function checkGaps(gaps: readonly Gap[]): Gap[] {\n for (const gap of gaps) {\n if (!(GAP_REASONS as readonly string[]).includes(gap.reason)) {\n throw new Error(\n `缺口台账的 reason 只有 ${GAP_REASONS.join('/')} 两个取值,拿到的是 ` +\n `${JSON.stringify(gap.reason)}(${gap.why})`,\n );\n }\n }\n return [...gaps];\n}\n\n/** → 前提级台账原样返回;`reason` 认不出来就**抛**(理由同 `checkReds`)。 */\nexport function checkPremise(problems: readonly Premise[]): Premise[] {\n for (const problem of problems) {\n if (!(PREMISE_REASONS as readonly string[]).includes(problem.reason)) {\n throw new Error(\n `前提级台账的 reason 不在取值域里:${JSON.stringify(problem.reason)}(${problem.message})`,\n );\n }\n }\n return [...problems];\n}\n\n/**\n * → 基线轮次这一档的前提级台账(`complete` ⇒ 空表)。\n *\n * 🔴 **判据只读 `inspectRound` 的结论,一个字都不重判**:门 0 自己比几份 roundId,\n * 就是把 `inspectRound` 花整轮治掉的那个洞重新引回来。\n * 🔴 **不认识的档位一律算「不完整」**(fail-closed):生产者哪天多一档而这里没跟上时,\n * 静默放行等于把没见过的状态当成 complete。\n * ⚠️ **这一档的出路要联网,说清楚**:`unknown`(更早版本爬的老基线)唯一的出路是重跑一次\n * `recon`,而它**要连客户内网的 HCM**。⇒ 在**拿不到网**的机器上这条路确实可能走不通 ——\n * 那是一条真实的现场约束。放行它不是选项:那等于把「判不了」当「完整」。\n */\nexport function baselineRoundPremise(round: Pick<RoundResult, 'state'> | null): Premise[] {\n const state = round && typeof round === 'object' ? round.state : undefined;\n if (state === 'complete') return [];\n return [\n {\n reason: REASON_BASELINE_ROUND_NOT_COMPLETE,\n message:\n `这份基线的轮次状态是 ${JSON.stringify(state)}(不是 complete)⇒ A 类事实是从**混着不同轮次 / ` +\n '判不了轮次**的产物里现算的,「判红 0 条」对哪一轮都不算数。' +\n '先跑 `hcm delivery recon round` 看是哪几份,或重跑一次 `hcm delivery recon`。' +\n '⚠️ **`hcm delivery recon` 要连客户内网的 HCM** —— 拿不到网的机器上这条出路走不通,' +\n '此时**别把这份基线当 complete 用**,把它带回连得上的地方重爬',\n },\n ];\n}\n\n/**\n * 这个 token 落不落在**判红这一层声明的缺口**里(纯数字 / 单字符)。\n *\n * 🔴 **不是「过滤噪声」而是「声明缺口」**:`7`(码表的 sortOrder)与 `男`(枚举 label)\n * 是真的 A 类取值,写进 skill 正文本来就该判红。这里放行它们是因为拿它们当判据会把任何\n * 一份中文文档淹成红海 —— 代价是这一档**门 0 恒绿**。落在这里的 token 逐条进 `gaps`,\n * 不许说成「已覆盖」。\n */\nexport function inGap(token: string): boolean {\n return [...token].length <= 1 || /^\\p{Nd}+$/u.test(token);\n}\n\nfunction allMatches(regex: RegExp, line: string): RegExpExecArray[] {\n const out: RegExpExecArray[] = [];\n const re = new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : `${regex.flags}g`);\n let m: RegExpExecArray | null;\n while ((m = re.exec(line)) !== null) {\n if (m[0] === '') {\n re.lastIndex += 1;\n continue;\n }\n out.push(m);\n }\n return out;\n}\n\ntype Span = readonly [number, number];\n\nfunction overlaps(span: Span, spans: readonly Span[]): boolean {\n return spans.some(([s, e]) => span[0] < e && s < span[1]);\n}\n\n/**\n * → 这一行里的端点 `[(token, span)]`,**两种形态都收**。\n *\n * 🔴 **这是端点抽取唯一的入口**:判据断在其中一条正则上、而另一条形态压根不存在时,\n * 「端点抽得出来」在用例里成立、在真样本上整类落空。token 一律是**路径部分**,\n * `span` 是**整段**(URL 形态含 scheme + host),后面几档正则靠它避让。\n */\nexport function endpointsIn(line: string): Array<[string, Span]> {\n const out: Array<[string, Span]> = [];\n for (const regex of [URL_ENDPOINT_RE, HOSTLESS_ENDPOINT_RE]) {\n for (const m of allMatches(regex, line)) {\n const span: Span = [m.index, m.index + m[0].length];\n if (\n !overlaps(\n span,\n out.map(([, s]) => s),\n )\n ) {\n out.push([m[1] as string, span]);\n }\n }\n }\n for (const m of allMatches(BARE_ENDPOINT_RE, line)) {\n const span: Span = [m.index, m.index + m[0].length];\n if (\n !overlaps(\n span,\n out.map(([, s]) => s),\n )\n ) {\n out.push([m[0] as string, span]);\n }\n }\n return out;\n}\n\n/**\n * 端点归一:`{model}` / `{id}` 这类模板段一律写成 `{}`。\n *\n * 🔴 **幂等**(`normalize(normalize(t)) === normalize(t)`)—— 占位段的正则用 `*` 而不是 `+`,\n * `{}` 自身归一后还是 `{}`。不幂等时「登记册里的 token」与「查表用的键」会随调用次数变形,\n * 而症状是豁免时灵时不灵。\n */\nexport function normalize(token: string): string {\n return token.replace(PLACEHOLDER_RE, '{}');\n}\n\n/**\n * → 查 `P` 用的模板段清单(每条是一个段数组)。\n *\n * 🔴 **这是那道接缝的落点**:登记册登记的是模板形态 `/api/models/{model}/meta`,\n * skill 正文里出现的是具体形态 `/api/models/Employee/meta`。按字符串相等查,`P` **恒不命中**,\n * 于是每一处端点提及都判假红 —— 而登记册那边全绿、抽取器那边也全绿,两边谁也发现不了。\n * 所以查表是**按段匹配**:段数相同、每一段要么逐字相等、要么登记的那一段是占位符。\n */\nexport function factsIndex(facts: readonly string[]): string[][] {\n return facts.map((token) => segmentsOf(normalize(token)));\n}\n\n/**\n * 这个 token 有没有被 `P` 覆盖(`token ∈ P` 的实际判据)。\n * 对没有斜杠的 token(错误码)退化成「与某条登记项逐字相等」。\n *\n * 🔴 **查询侧那个 `normalize` 是冗余的,别把它读成一道守卫**:`{}` 吃任意一段这一条\n * **不足以**推出冗余,还得有「抽取器产出的 token 里,花括号只可能**占满整段**」这一条\n * (它来自端点正则的段交替式)。两条合起来才推得出「查询侧归一改变不了任何结果」。\n * 留着这次调用只是为了两侧口径出自同一个函数。真正承重的是下面按段匹配本身。\n */\nexport function covered(token: string, index: readonly string[][]): boolean {\n const segments = segmentsOf(normalize(token));\n return index.some(\n (template) =>\n template.length === segments.length &&\n template.every((t, i) => t === '{}' || t === segments[i]),\n );\n}\n\nexport const SEGMENT_SEPARATOR = '/';\n\n/**\n * 把一个路径形态的 token 切成段。**路径分段这件事只有这一个出处**。\n *\n * 加这个函数不是为了好看:`artifactReason` 与用例里那几条「放宽面」谓词各自写\n * `token.split('/')` 时,「怎么切段」有好几份载体,而判据那一侧的载体一旦漂了,守卫看不见。\n */\nexport function segmentsOf(token: string): string[] {\n return token.split(SEGMENT_SEPARATOR);\n}\n\n/**\n * 这个 token 是不是产物树里的路径片段(**无豁免**);不是就返 `null`。\n *\n * · `product-tree-path` —— **从产物根锚定**:有一段是 `.hcmnx`,或有一段是 `recon` 且它\n * 后面还有至少两段。这一档几乎不会误伤:它要求路径长得像**真的产物树**。\n * · `product-relative-path` —— **恰好两段**,且(第一段, 末段后缀)是真产物**真有**的那几对。\n * · `product-file-name` —— 文件名以 `.json` / `.jsonl` 收尾。这一档是**过判**:\n * `package.json` 也会红。留着它是因为产物**全部**是 json/jsonl,而收窄成「白名单文件名」\n * 就成了一张没人维护的登记表(产物多一份、白名单不动,那一份从此免检)。\n * ⚠️ 拆成独立 reason 就是为了让读的人分得清「这条是真产物路径」与「这条只是恰好以\n * .json 收尾」—— 两者该不该照着改并不一样。\n */\nexport function artifactReason(token: string): string | null {\n const segments = segmentsOf(token);\n const head = segments.slice(0, -1);\n // `split` 至少给一段,所以末段一定在;写出来是为了让类型收窄,不是防御一个走得到的分支。\n const last = segments[segments.length - 1] ?? '';\n if (head.includes(ARTIFACT_ROOT)) return REASON_PRODUCT_TREE_PATH;\n for (let i = 0; i < head.length; i += 1) {\n if (head[i] === RECON_DIRNAME && segments.length - i - 1 > RECON_SEGMENTS_BELOW_ROOT) {\n return REASON_PRODUCT_TREE_PATH;\n }\n }\n if (\n segments.length === 2 &&\n PRODUCT_RELATIVE_SHAPES.some(\n ([subdir, suffix]) => segments[0] === subdir && last.endsWith(suffix),\n )\n ) {\n return REASON_PRODUCT_RELATIVE_PATH;\n }\n if (ARTIFACT_SUFFIXES.some((suffix) => last.endsWith(suffix))) return REASON_PRODUCT_FILE_NAME;\n return null;\n}\n\n/**\n * 汉字区段(**只认表意文字,不含中文标点**)。标点是**真边界**:`「参加工作时间」`\n * 两侧的书名号不该把它推进「落在更长的中文串里」那一桶。\n */\nconst CJK_RANGES: ReadonlyArray<readonly [number, number]> = [\n [0x3400, 0x4dbf],\n [0x4e00, 0x9fff],\n [0xf900, 0xfaff],\n];\n\nexport function isCjk(char: string | undefined): boolean {\n const code = char?.codePointAt(0);\n return code !== undefined && CJK_RANGES.some(([low, high]) => code >= low && code <= high);\n}\n\n/**\n * → `token` 在这一行里**按分词边界**出现的位置;没有就 -1。\n *\n * 🔴 边界只在**词字符**那一侧成立:`hireDate` 出现在 `myhireDates` 里不算数,\n * 而 `已离职` 出现在 `已离职员工` 里**算数**(CJK 侧没有边界,见缺口②)。\n * ⚠️ 「算数」不等于「判红」—— 它被分进第二个桶,见 `cjkRunAt`。\n */\nexport function boundedFind(line: string, token: string): number {\n let start = line.indexOf(token);\n while (start >= 0) {\n const end = start + token.length;\n const leftOk =\n start === 0 || !(isWordChar(token[0]) && isWordChar(line[start - 1]));\n const rightOk =\n end >= line.length || !(isWordChar(token[token.length - 1]) && isWordChar(line[end]));\n if (leftOk && rightOk) return start;\n start = line.indexOf(token, start + 1);\n }\n return -1;\n}\n\n/**\n * 这一处命中是不是**落在更长的中文串里**(缺口②的分桶判据)。\n *\n * 🔴 中文没有分词边界,所以这一档**分桶而非分对错**:token 的边是汉字、而紧挨着它的那个字\n * 也是汉字 ⇒ 说不准(`数字员工` 里的 `员工`)。**假红会让顾问把门 0 整个关掉,比漏扫更糟**。\n * ⇒ 两侧都不是汉字 ⇒ 照常判红;任一侧是汉字 ⇒ 进 `gaps`,**不顶 rc,但逐条印出**。\n */\nexport function cjkRunAt(line: string, start: number, token: string): boolean {\n const end = start + token.length;\n if (isCjk(token[0]) && start > 0 && isCjk(line[start - 1])) return true;\n return isCjk(token[token.length - 1]) && end < line.length && isCjk(line[end]);\n}\n\n/** A 类七子集 → 逐行命中。两条实现路径见模块 docstring。 */\nexport class Matcher {\n readonly owners: Map<string, AclassKind[]>;\n private readonly simple: Set<string>;\n private readonly byFirst: Map<string, string[]>;\n\n constructor(aclass: Partial<Aclass>) {\n this.owners = new Map();\n for (const kind of ACLASS_KINDS) {\n for (const token of [...(aclass[kind] ?? [])].sort()) {\n const owned = this.owners.get(token);\n if (owned) owned.push(kind);\n else this.owners.set(token, [kind]);\n }\n }\n this.simple = new Set([...this.owners.keys()].filter((t) => SIMPLE_TOKEN.test(t)));\n this.byFirst = new Map();\n for (const token of this.owners.keys()) {\n if (this.simple.has(token)) continue;\n const first = token[0];\n if (first === undefined) continue; // 空 token 进不了集合,这里只是让类型收窄\n const bucket = this.byFirst.get(first);\n if (bucket) bucket.push(token);\n else this.byFirst.set(first, [token]);\n }\n }\n\n /**\n * → 这一行命中的 `[(token, **有边界的**那一处的位置)]`,同一 token 一行只报一次。\n *\n * 🔴 **位置必须来自 `boundedFind`,不许再算一遍 `indexOf`**。起手那版用 `boundedFind`\n * 确认「这一行**存在**一处合法边界命中」,返回的却是第一处裸子串 —— 可能根本不是那一处。\n * 下游把这个位置交给 `cjkRunAt` 做分桶,于是**在同一行前面加一处诱饵写法就能把一条\n * 真红压掉**。辖区 = 首/末字符为 CJK、另一端为词字符的**混合形态** token;纯 ASCII 与\n * 纯 CJK 两个位置必然相等,所以此前每一项变异都照不到它。\n * 「存在一处合法命中」是「这一处合法」的**必要条件**,不是充分条件。\n *\n * 🔴 **`sort` 不是洁癖,它是这份输出的确定性本身**:按插入序遍历会让非纯词形 token 的\n * 先后随输入变化,集合、rc、判词全都一样,只有顺序变 —— 那正是「静默」的定义,\n * 而它坏的是**验证仪器**:任何「前后两版输出逐字相等」的复核都会假红。\n */\n hits(line: string): Array<[string, number]> {\n const seen = new Set<string>();\n const result: Array<[string, number]> = [];\n for (const m of allMatches(WORD_RE, line)) {\n // 词正则切出来的是**极大词段**,所以它必然是有边界的那一处;即便如此也走同一个\n // `boundedFind`,不给「算位置」留第二份实现。\n const token = m[0];\n if (token !== undefined && this.simple.has(token) && !seen.has(token)) {\n seen.add(token);\n result.push([token, boundedFind(line, token)]);\n }\n }\n for (const char of [...new Set([...line])].sort()) {\n for (const token of this.byFirst.get(char) ?? []) {\n if (seen.has(token)) continue;\n const start = boundedFind(line, token);\n if (start >= 0) {\n seen.add(token);\n result.push([token, start]);\n }\n }\n }\n return result;\n }\n}\n\n/**\n * → `{类: reason}`,只看 `aclass.ts` 的**类级**台账。\n *\n * 🔴 拿它只为把「为什么这一类是空的」说清楚 —— **判前提不成立不靠它**,靠的是\n * `aclass[kind]` 本身空不空(那是直接观测,不依赖台账写得对不对)。\n */\nfunction classReasons(problems: readonly AclassProblem[] | undefined): Map<AclassKind, string> {\n const out = new Map<AclassKind, string>();\n for (const problem of problems ?? []) {\n if (problem.scope === CLASS_SCOPE) {\n for (const kind of problem.kinds) out.set(kind, problem.reason);\n }\n }\n return out;\n}\n\n// ── 同形不同码的归一(缺口⑨ 修了的那两样)────────────────────────────────────\n//\n// 🔴 **不可见的格式控制字符:删;判据是 Unicode 通用类别 `Cf`**,不是一张手写码位表。\n// 起手写的是五个码位,在 `Cf` 全族面前**只覆盖一小半**:U+00AD SOFT HYPHEN\n//(最经典的 Word 粘贴产物)、U+200E/200F(RTL 标记,本产品带 `ar` 语种)等整类逃过。\n// **手写名单漏登永远不红**。\n// 🔴 删它**只能**把看起来连着的东西变成真的连着 ⇒ **不产生新假红**;但同一个「接起来」\n// 机制会把判定往松的方向搬(两段中文被接起来 ⇒ 缺口② 翻转;全角字母映回 ASCII ⇒\n// 词边界翻转)—— 那一半由 `judgeLine` 的「**归一只加不减**」兜住。\nconst INVISIBLE_RE = /\\p{Cf}/u;\n\n/**\n * 全角**只映射字母与数字三段**,不动标点。这一半是**长度保持的 1:1 映射** ⇒ 它单独作用时\n * 所有 span 语义原样成立。\n * 🔴 **不做完整 NFKC**,两条都是实测,不是直觉:\n * ① NFKC 会把全角**标点**一并改写(全角括号、全角冒号、全角加号),而全角冒号参与\n * scheme-less host 的「名字:端口」形态、加号参与 naturalKey 的组合形态\n * (`code+orgUnitCode`)⇒ 端点抽取与通道一的口径都要重核;而那些全角标点在出厂\n * skill 里是**真有的**。它会动的还不止标点,还有带圈数字 —— 而带圈数字正是本模块\n * 缺口清单的**编号载体**。\n * ② U+F900–FAFF 这一段**恰好在 `CJK_RANGES` 里**,而它里面**有**码位 NFKC 之后落到\n * `CJK_RANGES` 之外 ⇒ `isCjk` 翻转,缺口② 的分桶跟着变。(是「有」不是「整段」。)\n */\n// KNOB: fullwidth-alnum-ranges —— 只映射字母与数字三段(放到标点会动端点与自然键的形态判定)\nexport const FULLWIDTH_ALNUM_RANGES: ReadonlyArray<readonly [number, number, number]> = [\n [0xff10, 0xff19, 0xff10 - 0x30],\n [0xff21, 0xff3a, 0xff21 - 0x41],\n [0xff41, 0xff5a, 0xff41 - 0x61],\n];\n\n/**\n * 归一之后才认出来的那一条红 / 缺口,**必须自己说出来**:不给它,顾问拿判词里那个词去\n * 文件里 grep 会**一无所获**,然后判定门 0 报了假红。\n * 🔴 **它带上原文里那一段的字面**:只说「grep 搜不到」是一句**没有出路**的话。\n */\nexport const FOLDED_NOTE =\n ';⚠️ 这一处是**归一之后**才认出来的:原文里夹了不可见字符或全角字符,' +\n '直接按上面这个词去 grep 会搜不到';\nconst foldedOriginal = (literal: string): string => `,原文里那一段是 ${JSON.stringify(literal)}`;\n\n/**\n * → `[归一后的行, 每个字符在原文里的下标]`。**归一只有这一个实现**。\n *\n * 判词要指回原文,就得知道归一后的某一段对应原文的哪一段。另写一份「带下标的归一」\n * 是本模块记过的老形态(两个载体各写各的)⇒ `foldConfusables` 直接取它的第一个返回值。\n */\nfunction foldWithMap(line: string): [string, number[]] {\n const out: string[] = [];\n const origin: number[] = [];\n for (let i = 0; i < line.length; i += 1) {\n const char = line[i] as string;\n if (INVISIBLE_RE.test(char)) continue;\n const code = char.charCodeAt(0);\n let folded = char;\n for (const [low, high, offset] of FULLWIDTH_ALNUM_RANGES) {\n if (code >= low && code <= high) {\n folded = String.fromCharCode(code - offset);\n break;\n }\n }\n out.push(folded);\n origin.push(i);\n }\n return [out.join(''), origin];\n}\n\n/**\n * 同形不同码 → 逐字节可比的形态。**幂等**(第二次调用是恒等变换)。\n *\n * 🔴 **不做 casefold**:A 类里**有** `code` 这一类通用词,casefold 之后英文散文里的\n * `Claude Code` / `Code-First` 这类写法会整片判红 —— 假红面在扩大,而扩出来的那一批与\n * 环境事实无关。⇒ 缺口⑨ 收窄成「**大小写变形仍不判**」,不许说成已覆盖。\n * ⚠️ **只归一被扫的那一行,不归一 A 类 token 自己**(那一档至今没修,见缺口⑨)。\n */\nexport function foldConfusables(line: string): string {\n return foldWithMap(line)[0];\n}\n\n/**\n * → 这条红 / 缺口该追加的判词(`token` 在原文里逐字找得到时是**空串**)。\n *\n * 🔴 **条件是「这个 token 在原文里找不到」,不是「这一行被归一改动过」**。两者不等价,\n * 而差出来的那一档会让判词**说假话**:一份带 BOM(`Cf` 类)的文件,整行都被归一改动过,\n * 可 `hireDate` **在原文里逐字就在那儿**、grep 找得到 —— 按行判的话会给它贴上\n * 「grep 会搜不到」。判词的整个立论是「一条把人指错方向的红比不红更贵」。\n */\nexport function foldedNoteFor(original: string, folded: string, token: string): string {\n if (original.includes(token)) return '';\n let start = boundedFind(folded, token);\n if (start < 0) start = folded.indexOf(token);\n if (start < 0) {\n // 防御分支:token 一定来自 `folded`,所以这里今天走不到;走到了也只是少一句\n // 「原文里那一段是 …」,不会说出假话。\n return FOLDED_NOTE;\n }\n const [, origin] = foldWithMap(original);\n const from = origin[start];\n const to = origin[start + token.length - 1];\n // `origin` 与 `folded` 等长,而 token 是从 `folded` 里找到的 ⇒ 两个下标今天都在界内。\n // 越界时只少一句「原文里那一段是 …」—— **不许**在这里编一段字面出来,那会让判词说假话。\n if (from === undefined || to === undefined) return FOLDED_NOTE;\n return FOLDED_NOTE + foldedOriginal(original.slice(from, to + 1));\n}\n\n/**\n * 一行的判决。**归一只加不减**。\n *\n * 🔴 **归一之后再判一次,红取并集**。只判归一后的行时,同一个「把字接起来」的机制在两个\n * 方向上生效,而只有一个方向被论证过 —— 实测造出两条新漏判(一条红降级成缺口②、\n * 一条因词边界翻转直接消失)。⚠️ 第二趟**只在归一后与原文不同时才跑** ⇒ 对正常中文\n * 文本零影响。⚠️ `lineno` 全程是**原文行号**:归一不跨行,行数不变。\n */\nfunction judgeLine(\n line: string,\n lineno: number,\n rel: string,\n matcher: Matcher,\n index: readonly string[][],\n reds: Red[],\n gaps: Gap[],\n): void {\n const folded = foldConfusables(line);\n const lineReds: Red[] = [];\n const lineGaps: Gap[] = [];\n judgePass(line, lineno, rel, matcher, index, lineReds, lineGaps);\n if (folded !== line) judgePass(folded, lineno, rel, matcher, index, lineReds, lineGaps);\n\n // 🔴 去重**保留先出现的那一条** —— 原文那一趟排在前面,它的 token 在原文里逐字找得到,\n // 判词也就不必带「归一之后才认出来的」那一句。\n const seen = new Set<string>();\n const unique: Red[] = [];\n for (const red of lineReds) {\n const key = `${red.channel} ${red.token}`;\n if (seen.has(key)) continue;\n seen.add(key);\n unique.push({ ...red, message: red.message + foldedNoteFor(line, folded, red.token) });\n }\n reds.push(...unique);\n\n // 🔴 **缺口只在两侧都没红时保留**:同一个 token 同时以「判红」和「说不准」两条自相矛盾的\n // 记录印出来,读的人无从取舍。\n // 🔴 **这道过滤只在并集路径上生效**。写成无条件时,它依赖的「单趟里 red 与 gap 对同一个\n // token 互斥」不是本函数保证的 —— 它靠 `Matcher.hits` **一 token 一位置**这个**别处的**\n // 实现,**而那恰恰是 `hits` 自己 docstring 里记着的历史 bug 形态**。前提一旦不成立,\n // 这里不会报「前提没成立」,只会**静默少一条缺口**。\n const redTokens = new Set(unique.filter((r) => r.channel === CHANNEL_ACLASS).map((r) => r.token));\n const seenGaps = new Set<string>();\n for (const gap of lineGaps) {\n const key = `${gap.reason} ${gap.token}`;\n if (seenGaps.has(key) || (folded !== line && redTokens.has(gap.token))) continue;\n seenGaps.add(key);\n // 🔴 **缺口这一档也贴判词**:它的判词写着「请自己看一眼」,而归一之后认出来的那个词\n // 在原文里**根本 grep 不到** —— 让人去看一个不存在的词,与 `FOLDED_NOTE` 要治的是\n // 同一件事。\n gaps.push({ ...gap, why: gap.why + foldedNoteFor(line, folded, gap.token) });\n }\n}\n\n/**\n * **一趟**判决:四条通道全部吃传进来的这一份行(原文或归一后,二选一)。\n *\n * 🔴 拆出来是为了让「一次判决只吃一份行」这句话有个落点:一半吃原文一半吃归一后的,\n * `span` 就对不上(删不可见字符那一半在删字符)。\n * 🔴 **顺序即语义**:通道一先判、且**不查 `P`**;只有通道一没认领的正则命中才去查 `P`。\n * 反过来写(先查 `P` 再判 A)就是那条被点名的减法 —— 登记过的 A 类 token 会被放行,\n * 与通道三自相矛盾。\n */\nfunction judgePass(\n line: string,\n lineno: number,\n rel: string,\n matcher: Matcher,\n index: readonly string[][],\n reds: Red[],\n gaps: Gap[],\n): void {\n for (const [token, start] of matcher.hits(line)) {\n const kinds = matcher.owners.get(token) ?? [];\n if (inGap(token)) {\n gaps.push({\n reason: GAP_NOISE_TOKEN,\n token,\n kinds,\n path: rel,\n line: lineno,\n why:\n `${rel}:${lineno} 纯数字 / 单字符 ${JSON.stringify(token)} —— ` +\n '判红这一层**声明的缺口**,查过、按缺口放行',\n });\n continue;\n }\n if (cjkRunAt(line, start, token)) {\n gaps.push({\n reason: GAP_CJK_INSIDE_LONGER_RUN,\n token,\n kinds,\n path: rel,\n line: lineno,\n why:\n `${rel}:${lineno} ${JSON.stringify(token)}(A 类 ${kinds.join('/')})落在一串更长的中文里 —— ` +\n '中文没有分词边界,**说不准**它是不是那个取值;不顶 rc,请自己看一眼',\n });\n continue;\n }\n reds.push({\n channel: CHANNEL_ACLASS,\n reason: REASON_ENVIRONMENT_FACT,\n token,\n path: rel,\n line: lineno,\n kinds,\n message:\n `${rel}:${lineno} 出现环境事实 ${JSON.stringify(token)}(A 类 ${kinds.join('/')})—— ` +\n '换个客户就全错,**无豁免**',\n });\n }\n const spans: Span[] = [];\n for (const [token, span] of endpointsIn(line)) {\n spans.push(span);\n judgeProtocol(token, REASON_ENDPOINT, '端点', lineno, rel, matcher, index, reds);\n }\n // 🔴 **产物路径排在错误码之前**:`valuedomain/CO_0001.json` 里的 `CO_0001` 逐字符合\n // 错误码的形态,按相反的顺序它会被错误码那一档先吃掉 —— **红照样红**,但消息把人指向\n // 登记册(「登记一条并挂探针」),而正确的下一步是「别在正文里写产物路径」。\n // ⚠️ 只有**产物路径**吃掉它所覆盖的那一段;`docs/BR_FOO.md` 这种非产物路径不吃,\n // 里面的错误码照常判 —— 反过来就是拿一个宽松的路径正则给错误码开后门。\n for (const regex of [PATHISH_RE, PRODUCT_FILE_RE]) {\n for (const m of allMatches(regex, line)) {\n const span: Span = [m.index, m.index + m[0].length];\n if (overlaps(span, spans)) continue;\n const token = m[0] as string;\n const reason = artifactReason(token);\n if (reason === null) continue;\n spans.push(span);\n reds.push({\n channel: CHANNEL_ARTIFACT,\n reason,\n token,\n path: rel,\n line: lineno,\n kinds: [],\n message:\n `${rel}:${lineno} ${artifactWordingFor(reason)(token)} —— 产物路径**无豁免**,` +\n '登记册不受理它;skill 正文该说的是跑哪条命令,不是去读哪个文件',\n });\n }\n }\n for (const m of allMatches(ERROR_CODE_RE, line)) {\n const span: Span = [m.index, m.index + m[0].length];\n if (overlaps(span, spans)) continue;\n spans.push(span);\n judgeProtocol(m[0] as string, REASON_ERROR_CODE, '错误码', lineno, rel, matcher, index, reds);\n }\n}\n\nfunction judgeProtocol(\n token: string,\n reason: string,\n shape: string,\n lineno: number,\n rel: string,\n matcher: Matcher,\n index: readonly string[][],\n reds: Red[],\n): void {\n // 通道一已经认领过它了 —— 同一处不判两遍。\n if (matcher.owners.has(token)) return;\n if (covered(token, index)) return;\n reds.push({\n channel: CHANNEL_UNREGISTERED,\n reason,\n token,\n path: rel,\n line: lineno,\n kinds: [],\n message:\n `${rel}:${lineno} 出现${shape} ${JSON.stringify(token)},而它**没有登记**在随包的协议事实` +\n '登记册里 ⇒ 要么改写成不依赖它的说法,要么给 CLI 提一条登记(登记必须附一个断行为的探针)',\n });\n}\n\nexport interface ScanResult {\n red: Red[];\n channels: Record<Channel, Red[]>;\n scanned: string[];\n gaps: Gap[];\n premise: Premise[];\n}\n\nexport interface ScanOptions {\n /** `loadProtocolFacts` 的台账。**不传 ⇒ 通道四一条都判不出来**,见下。 */\n factsProblems?: readonly FactsProblem[];\n /** `deriveAclass` 的台账。 */\n aclassProblems?: readonly AclassProblem[];\n}\n\n/**\n * → `{red, channels, scanned, gaps, premise}`。\n *\n * `skillRoot` = 一个 skill 的根目录。**扫描面 = 递归走得到的全部文件**(skill 正文 +\n * `references/` 全目录 + 别的任何子目录,同一判据)。\n * 🔴 **点目录不排除**:`grep -r` 进不了点目录、会假报零命中 —— 扫描面漏掉一支不会让任何\n * 东西红,「没扫到」与「扫过、干净」逐字同形。\n * 🔴 **「递归」这个词有一处例外**:**不进符号链接目录** ⇒ 软链目录 / 环路软链 / 断链\n * **不跟随**,但**会被逐条判成前提级**(rc=2),不再静默。指向**文件**的软链照旧扫。\n *\n * `factsProblems` **有默认值,但那个默认值不代表「查过了」**:不传 ⇒ 通道四一条都判不出来,\n * 而「登记册干干净净」与「根本没查登记册」在返回值上逐字同形 ⇒ 走到缺省就必然多一条\n * 前提级台账。\n * `aclassProblems` **两个去处**:类级那一档只用来把消息说清楚;**条目级那一档升前提**\n * (`aclass-partially-unread`)—— 此前条目级被整类吞掉 ⇒ 一行坏数据里的环境事实逐字写进\n * skill 也判不出来,而门 0 说「判红 0 条」、rc=0,是完整的静默假绿。\n */\nexport function scan(\n skillRoot: string,\n aclass: Partial<Aclass>,\n facts: readonly string[],\n options: ScanOptions = {},\n): ScanResult {\n const reds: Red[] = [];\n const premise: Premise[] = [];\n const gaps: Gap[] = [];\n const scanned: string[] = [];\n const matcher = new Matcher(aclass);\n const index = factsIndex(facts);\n\n // ── 前提①:A 类子集为空 ⇒ 那一类的门 0 恒绿 ────────────────────────────────\n // 🔴 `deriveAclass` 不传映射工件 ⇒ `naturalKey` 恒空 ⇒ **必须** rc=2。\n // 「台账里记了一笔」不等于「会让谁红」。\n const reasons = classReasons(options.aclassProblems);\n for (const kind of ACLASS_KINDS) {\n if ((aclass[kind]?.size ?? 0) > 0) continue;\n const reason = reasons.get(kind);\n // `reason === undefined` = 台账没传进来 / 这一类不在类级台账里(真的没成因可说);\n // 拿到了 reason 就必须说得出那一档的话 ⇒ 少一档当场炸,不静默回落成「什么都不说」。\n let why = '';\n if (reason !== undefined) {\n if (!(reason in ACLASS_EMPTY_WHY)) {\n throw new Error(\n `类级成因 ${JSON.stringify(reason)} 没有对应的措辞(认得的是 ${CLASS_REASONS.join('/')})`,\n );\n }\n why = ACLASS_EMPTY_WHY[reason] as string;\n }\n premise.push({\n reason: REASON_ACLASS_EMPTY,\n message: `A 类子集 ${kind} 是空的${why} ⇒ 门 0 对这一类**恒绿**,这一轮的绿不算数`,\n });\n }\n\n // ── 前提①b:A 类**少了一部分**(条目级台账)⇒ 同样不可信 ─────────────────────\n // 🔴 判据打在**台账**上而不是打在「这一类空不空」上:条目级的定义就是「这一类整体仍然\n // 有人守」,所以那一类非空 —— 前提①那条永远够不着它。\n for (const problem of options.aclassProblems ?? []) {\n if (problem.scope !== ENTRY_SCOPE) continue;\n premise.push({\n reason: REASON_ACLASS_PARTIALLY_UNREAD,\n message:\n `A 类子集 ${problem.kinds.join('/')} 少了一部分(${problem.reason}):${problem.message} ` +\n '⇒ 少掉的那部分门 0 判不出来,这一轮的绿不算数',\n });\n }\n\n // ── 前提②:登记册这一层立没立住 ────────────────────────────────────────────\n let factsProblems = options.factsProblems;\n if (factsProblems === undefined) {\n premise.push({\n reason: REASON_CHANNEL_FOUR_UNCHECKED,\n message:\n '没有把 loadProtocolFacts 的台账传进来 ⇒ 通道四(登记项被拒)一条都没查过' +\n '——「登记册干净」与「没查登记册」在返回值上同形',\n });\n factsProblems = [];\n }\n for (const problem of factsProblems) {\n if (problem.scope === REGISTRY_SCOPE && PREMISE_REGISTRY_REASONS.includes(problem.reason)) {\n premise.push({\n reason: REASON_REGISTRY,\n message: `登记册这一层没立住(${problem.reason}):${problem.message} ⇒ 豁免通道 P 不可信`,\n });\n }\n }\n\n // ── 通道四:被拒的登记项 ───────────────────────────────────────────────────\n for (const problem of factsProblems) {\n if (problem.scope === REGISTRY_SCOPE) continue;\n reds.push({\n channel: CHANNEL_NO_PROBE,\n reason: REASON_REJECTED_REGISTRATION,\n token: problem.token ?? `(第${problem.index}条)`,\n path: null,\n line: 0,\n kinds: [],\n message: `登记册第 ${problem.index} 条被拒(${problem.reason}):${problem.message}`,\n });\n }\n\n // ── 通道三:P ∩ A —— 登记错了类 ────────────────────────────────────────────\n for (const token of [...facts].sort()) {\n const kinds = matcher.owners.get(token);\n if (!kinds) continue;\n reds.push({\n channel: CHANNEL_REGISTERED_IS_ACLASS,\n reason: REASON_REGISTERED_IS_ACLASS,\n token,\n path: null,\n line: 0,\n kinds,\n message:\n `登记册登记了 ${JSON.stringify(token)},而它是 A 类环境事实(${kinds.join('/')})—— ` +\n 'recon 查得到的东西不是版本级协议,登记它买不到任何豁免',\n });\n }\n\n // ── 扫描面 ────────────────────────────────────────────────────────────────\n let files: string[] = [];\n if (!isDirectory(skillRoot)) {\n premise.push({\n reason: REASON_NO_SKILL_ROOT,\n message: `扫描面 ${skillRoot} 不是一个目录 ⇒ 一个字都没扫过(这不是「扫过、干净」)`,\n });\n } else {\n // 🔴 **一次遍历,两个用途**:此前这里只留了「是文件」那一支,落选的条目**连一条台账\n // 都没有** —— 而落选的里面有整目录整目录的正文。\n const walked = walk(skillRoot);\n files = walked.files;\n for (const link of walked.unscannedLinks) {\n let target: string;\n try {\n target = readlinkSync(link);\n } catch {\n target = '(读不出链接目标)';\n }\n premise.push({\n reason: REASON_SYMLINK_NOT_SCANNED,\n message:\n `${path.relative(skillRoot, link)} 是一个符号链接(指向 ${JSON.stringify(target)})⇒ ` +\n '门 0 **没有顺着它扫过一个字**,这一轮的绿不算数。出路:把这条链从 skill 里去掉 —— ' +\n '链后面那份内容本该被判就 `cp -RL` 拷成真内容(⚠️ 环路与断链上它会当场报错,那两种直接删);' +\n '内容已经在 skill 里(指向同树的别名)也直接删。' +\n '**不跟随**是刻意的 —— 跟过去会连上环路、或越出 skill 目录扫到无关文件',\n });\n }\n if (files.length === 0) {\n premise.push({\n reason: REASON_NOTHING_SCANNED,\n message: `扫描面 ${skillRoot} 在盘上,但里面一个文件都没有 —— 查过且为空`,\n });\n }\n }\n for (const file of files) {\n let text: string;\n try {\n text = readFileSync(file, 'utf8');\n } catch (e) {\n premise.push({\n reason: REASON_FILE_UNREADABLE,\n message: `${file} 读不动(${String(e)})⇒ 判不了它干不干净,门 0 不能对着它说绿`,\n });\n continue;\n }\n const rel = path.relative(skillRoot, file);\n scanned.push(rel);\n text.split('\\n').forEach((line, i) => {\n judgeLine(line, i + 1, rel, matcher, index, reds, gaps);\n });\n }\n\n // 同一 (通道, token, 文件, 行) 只报一次;顺序保持扫描顺序。\n const seen = new Set<string>();\n const unique: Red[] = [];\n for (const red of checkReds(reds)) {\n const key = `${red.channel} ${red.token} ${red.path} ${red.line}`;\n if (seen.has(key)) continue;\n seen.add(key);\n unique.push(red);\n }\n // 🔴 五个键**恒在**(同 `deriveAclass` 的七个键):调用方按 `CHANNELS` 遍历时,\n // 「这条通道一条都没判红」与「根本没有这条通道」必须是两个状态。\n const channels = Object.fromEntries(\n CHANNELS.map((name) => [name, unique.filter((r) => r.channel === name)]),\n ) as Record<Channel, Red[]>;\n return {\n red: unique,\n channels,\n scanned,\n gaps: checkGaps(gaps),\n premise: checkPremise(premise),\n };\n}\n\nfunction isDirectory(target: string): boolean {\n try {\n return statSync(target).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * 递归走扫描面。**不跟随符号链接目录**,但把它们逐条交出来(缺口⑧)。\n *\n * 指向**文件**的软链照旧算文件(`statSync` 跟随软链),与 kit 侧口径一致。\n * 返回的路径**全局排序**,好让 `scanned` 的顺序与判词的先后是确定的。\n */\nfunction walk(root: string): { files: string[]; unscannedLinks: string[] } {\n const files: string[] = [];\n const unscannedLinks: string[] = [];\n const visit = (dir: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n let followed: ReturnType<typeof statSync> | null = null;\n try {\n followed = statSync(full);\n } catch {\n followed = null; // 断链:跟过去就没了\n }\n if (followed?.isFile()) {\n files.push(full);\n } else if (entry.isSymbolicLink()) {\n unscannedLinks.push(full);\n } else if (followed?.isDirectory()) {\n visit(full);\n }\n }\n };\n visit(root);\n return { files: files.sort(), unscannedLinks: unscannedLinks.sort() };\n}\n\n/**\n * → 三档退出码。**不许合并**。\n *\n * 🔴 前提级**压过**判红:前提不成立时那份判红清单本身不可信。判红清单照打 ——\n * rc 是给脚本的闸,清单是给人的依据。\n */\nexport function exitCode(result: Pick<ScanResult, 'premise' | 'red'>): number {\n if (result.premise.length > 0) return 2;\n if (result.red.length > 0) return 1;\n return 0;\n}\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["token","token","fs","path","fs","yaml","yaml","fs","yaml","ExitCode","CliErrorCode","yaml","fs","fs","path","yaml","yaml","path","action","yaml","yaml","fs","os","path","yaml","token","token","resolve","reject","randomUUID","randomUUID","resolve","fs","fs","path","manifest","buildMiniAppTemplateFiles","manifest","asRecord","action","path","yaml","JSON_SCHEMA","manifest","path","yaml","JSON_SCHEMA","path","createHash","readFileSync","path","createHash","isPlainObject","sha256","path","randomBytes","existsSync","readFileSync","path","modelKeys","path","modelKeys","path","action","WORDING","modelKeys","AUDIT_SET","WORDING","isRecord","typeName","modelKeys","path","existsSync","path","isRecord","WORDING","isRecord","modelEntries","path","PATH_SEPARATORS","randomBytes","isRecord","readFileSync","existsSync","state","enumRows","existsSync","readFileSync","readdirSync","statSync","path","readFileSync","isRecord","readFileSync","existsSync","statSync","readdirSync","existsSync","readFileSync","path","ENTRY_SCOPE","ENTRY_SCOPE","existsSync","readFileSync","readFileSync","readdirSync","statSync","path","PLACEHOLDER_RE","token","unique","walk","readFileSync","statSync","readdirSync"]}
|