feat: classify Host dependency exports
This commit is contained in:
parent
c55beac34a
commit
943a544899
4 changed files with 98 additions and 8 deletions
|
|
@ -2,5 +2,5 @@
|
|||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-26-published-dependency-faces.md
|
||||
2026-08-26-published-dependency-faces.md: 04b02afe372b2f3d90729f17e837b40ff1e0e6fa
|
||||
2026-08-26-published-dependency-faces.zh.md: 468fb443e2e94bf23f8c61201f43c2a82f7f509b
|
||||
2026-08-26-published-dependency-faces.md: a4c745e8bc811027791317b334d285274f24dd20
|
||||
2026-08-26-published-dependency-faces.zh.md: 73b8dd28f40a63ea2a13544be535be7832138ba2
|
||||
|
|
|
|||
|
|
@ -30,11 +30,40 @@ Workspace imports used by the Client bundle, type-only imports, module augmentat
|
|||
|
||||
The verifier reads source manifests and source files, so it runs on a clean tree without built `lib/`. An unclassified Host runtime export is a policy violation that blocks all `--fix` writes; a maintainer must review the export and classify it, change the source relationship, or change the package selection. Once source safety passes, `--fix` performs only the section and range changes implied by the classification and removes stale peer metadata.
|
||||
|
||||
### Maintainer workflow
|
||||
|
||||
Run the verifier without `--fix` for a read-only check of package selection, export classifications, dependency sections, workspace ranges, and peer metadata. An unclassified runtime import reports one clickable `path:line:column` diagnostic per imported export.
|
||||
|
||||
```sh
|
||||
pnpm run verify-package-dependencies
|
||||
```
|
||||
|
||||
Classify each new Host runtime export in [`package-dependency-policy.ts`](../../../../scripts/package-dependency-policy.ts) before generating manifests. `safeHostDependencyExports` permits an ordinary dependency; `peerRequiredHostExports` keeps the whole provider package edge in matching peer and development sections. An export may appear in exactly one table. After refactoring a peer-required export so duplicate package copies are safe, move that exact specifier and export to the safe table; an edge becomes an ordinary dependency only after none of its imported exports remain peer-required.
|
||||
|
||||
Generate managed manifest sections, refresh the lockfile separately, and review both results. `--fix` writes nothing while a policy violation exists; after success it prints the ordinary-dependency and peer-required edge lists, but does not update the lockfile.
|
||||
|
||||
```sh
|
||||
pnpm run verify-package-dependencies -- --fix
|
||||
pnpm install --lockfile-only
|
||||
git diff -- packages pnpm-lock.yaml
|
||||
```
|
||||
|
||||
Measure the working-tree graph and a Git ref through the local metadata-only registry. Each run creates a fresh consumer and npm cache, executes `npm install --package-lock-only`, rejects archive downloads, and leaves the repository unchanged. `--runs` controls repetitions, `--timeout-ms` bounds each run, and optional `--max-ms` makes the command fail when the slowest run exceeds a threshold.
|
||||
|
||||
```sh
|
||||
pnpm run benchmark:npm-resolution -- --runs=5 --timeout-ms=300000
|
||||
pnpm run benchmark:npm-resolution -- --ref=origin/master --runs=5 --timeout-ms=300000
|
||||
```
|
||||
|
||||
Rank the next Host package by applying the current policy in memory, measuring a baseline, trying each reachable unconfigured package, and serially retesting the fastest coarse candidates. Positive `gainSeconds` is `baseline median - candidate median`; `--candidates` limits the roster, `--jobs` controls coarse concurrency, and neither phase writes manifests. A selected candidate still requires export classification before it joins `hostPackages`.
|
||||
|
||||
```sh
|
||||
pnpm run benchmark:npm-resolution:next -- --runs=1 --finalist-runs=5 --finalists=5 --jobs=8 --timeout-ms=120000
|
||||
```
|
||||
|
||||
### Performance verification
|
||||
|
||||
[`benchmark-next-package-dependency`](../../../../scripts/benchmark-next-package-dependency.ts) applies the current policy to an in-memory local registry, measures the current CLI graph, and tries every reachable unconfigured Host package one at a time. Concurrent runs provide a coarse shortlist; finalists run serially because npm's peer-placement search can take different paths when metadata request completion order changes.
|
||||
|
||||
The benchmark is manual rather than a CI gate. It performs metadata-only installs in fresh consumers, so its relative results identify peer relays without measuring registry latency or archive downloads.
|
||||
[`benchmark-npm-resolution`](../../../../scripts/benchmark-npm-resolution.ts) and [`benchmark-next-package-dependency`](../../../../scripts/benchmark-next-package-dependency.ts) remain manual rather than CI gates because resolver time varies with machine load and metadata completion order. Their fresh-consumer, metadata-only runs isolate npm's dependency-tree calculation from registry latency and archive downloads, so relative results identify peer relays without creating a release-time performance promise.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
|
|
|||
|
|
@ -30,11 +30,40 @@ Client bundle 使用的 workspace import、纯类型 import、模块扩充、`ds
|
|||
|
||||
验证器读取源码 manifest 和源码文件,因此可以在没有已构建 `lib/` 的干净工作树上运行。未分类的 Host 运行期导出属于策略违规,会阻止 `--fix` 的全部写入;维护者必须审查该导出,并选择分类该导出、修改源码关系或修改选包范围。源码安全检查通过后,`--fix` 只执行分类所确定的区段与范围变更,并删除失效的 peer 元数据。
|
||||
|
||||
### 维护流程
|
||||
|
||||
不带 `--fix` 运行验证器,会以只读方式检查选包范围、导出分类、依赖区段、workspace range 与 peer metadata。未分类的运行期 import 会按每个导出分别报告可点击的 `path:line:column` 诊断。
|
||||
|
||||
```sh
|
||||
pnpm run verify-package-dependencies
|
||||
```
|
||||
|
||||
生成 manifest 前,在 [`package-dependency-policy.ts`](../../../../scripts/package-dependency-policy.ts) 中分类每个新增 Host 运行期导出。`safeHostDependencyExports` 允许普通 dependency;`peerRequiredHostExports` 让整个提供包依赖边保留在范围一致的 peer 与开发区段。一个导出只能出现在一个表中。把 peer-required 导出重构到重复安装安全后,将该精确 specifier 与导出移入 safe 表;只有当一条依赖边的所有 import 都不再使用 peer-required 导出时,它才会成为普通 dependency。
|
||||
|
||||
生成受管 manifest 区段后,单独刷新 lockfile,并审查两部分结果。存在策略违规时,`--fix` 不写任何文件;成功后,它会分别打印普通 dependency 与 peer-required 依赖边,但不会更新 lockfile。
|
||||
|
||||
```sh
|
||||
pnpm run verify-package-dependencies -- --fix
|
||||
pnpm install --lockfile-only
|
||||
git diff -- packages pnpm-lock.yaml
|
||||
```
|
||||
|
||||
通过仅 metadata 的本地 registry 测量工作树依赖图与 Git ref。每轮都会创建全新 consumer 与 npm cache,执行 `npm install --package-lock-only`,拒绝下载包归档,并保持仓库不变。`--runs` 控制重复次数,`--timeout-ms` 限制单轮耗时,可选 `--max-ms` 会在最慢一轮超过阈值时让命令失败。
|
||||
|
||||
```sh
|
||||
pnpm run benchmark:npm-resolution -- --runs=5 --timeout-ms=300000
|
||||
pnpm run benchmark:npm-resolution -- --ref=origin/master --runs=5 --timeout-ms=300000
|
||||
```
|
||||
|
||||
计算下一项 Host 包时,命令会在内存中应用当前策略、测量 baseline、逐个尝试可达且未配置的包,并串行复测粗筛中最快的候选。正数 `gainSeconds` 等于 `baseline median - candidate median`;`--candidates` 限定名册,`--jobs` 控制粗筛并发度,两个阶段都不写 manifest。选中的候选仍需先完成导出分类,才能加入 `hostPackages`。
|
||||
|
||||
```sh
|
||||
pnpm run benchmark:npm-resolution:next -- --runs=1 --finalist-runs=5 --finalists=5 --jobs=8 --timeout-ms=120000
|
||||
```
|
||||
|
||||
### 性能验证
|
||||
|
||||
[`benchmark-next-package-dependency`](../../../../scripts/benchmark-next-package-dependency.ts) 把当前策略应用到内存中的本地 registry,测量当前 CLI 依赖图,并逐个尝试每个可达且未配置的 Host 包。并发运行用于得到粗筛名单;由于 metadata 请求的完成顺序会让 npm 的 peer 放置搜索走不同路径,最终候选会串行复测。
|
||||
|
||||
Benchmark 是手动诊断工具而非 CI 门禁。它在全新 consumer 中执行仅 metadata 的安装,因此相对结果可以定位 peer 中继,但不测量 registry 延迟或包归档下载。
|
||||
[`benchmark-npm-resolution`](../../../../scripts/benchmark-npm-resolution.ts) 与 [`benchmark-next-package-dependency`](../../../../scripts/benchmark-next-package-dependency.ts) 保持为手动工具而非 CI 门禁,因为 resolver 耗时会随机器负载和 metadata 完成顺序变化。它们通过全新 consumer 和仅 metadata 的运行,把 npm 依赖树计算与 registry 延迟、包归档下载分离,因此相对结果可以定位 peer 中继,但不构成发布时性能承诺。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const CLIENT_FACE_INCLUDE: readonly string[] = []
|
|||
/** Packages exempted from automatic Client/Host treatment despite declaring `dsh.client`. */
|
||||
const CLIENT_FACE_EXCLUDE: readonly string[] = [
|
||||
'@deepseek-ai/dsh-api-session-controller',
|
||||
'@deepseek-ai/dsh-api-workspace-controller',
|
||||
]
|
||||
|
||||
/** Host-only packages whose peer relays are deliberately flattened. */
|
||||
|
|
@ -14,11 +15,40 @@ const HOST_DEPENDENCY_PACKAGES: readonly string[] = [
|
|||
'@deepseek-ai/dsh-session',
|
||||
]
|
||||
|
||||
/**
|
||||
* Runtime exports whose values remain valid when npm installs another package copy.
|
||||
*/
|
||||
const SAFE_HOST_DEPENDENCY_EXPORTS = {
|
||||
'@deepseek-ai/dsh-api-session-controller/remote-events': ['SESSION_CONTROLLER_REMOTE_EVENTS'],
|
||||
'@deepseek-ai/dsh-credentials': ['credentialKey'],
|
||||
'@deepseek-ai/dsh-host-apiproxy': ['toFetchHandler'],
|
||||
'@deepseek-ai/dsh-host-apiproxy/api': ['RpcId', 'clientRequestSchema'],
|
||||
'@deepseek-ai/dsh-llm': ['MessageId', 'callConfigEquals', 'deepFreeze', 'freezeMessage'],
|
||||
'@deepseek-ai/dsh-llm/brand': ['ToolCallId'],
|
||||
'@deepseek-ai/dsh-session': ['isJsonValue'],
|
||||
'@deepseek-ai/dsh-settings': ['settingsNamespace'],
|
||||
'@deepseek-ai/dsh-system-prompt': ['FIRST_PARTY_SECTION_ORDER'],
|
||||
'@deepseek-ai/dsh-timeout': ['MAX_TIMER_DELAY_MS'],
|
||||
'@deepseek-ai/dsh-util-crypto': ['randomUUID'],
|
||||
'@deepseek-ai/schemastery': ['default'],
|
||||
} as const satisfies HostDependencyExports
|
||||
|
||||
/** Runtime exports that require every consumer to resolve the provider's shared peer instance. */
|
||||
const PEER_REQUIRED_HOST_EXPORTS = {
|
||||
'@deepseek-ai/dsh-scope': ['carrierKeyOf', 'scopeOf', 'scopeTarget'],
|
||||
'@deepseek-ai/dsh-typert-protocol': ['TypertLookupFailure', 'TypertRemoteFailure', 'remoteMethods'],
|
||||
} as const satisfies HostDependencyExports
|
||||
|
||||
/** Exact import specifier to reviewed runtime exports. */
|
||||
type HostDependencyExports = Readonly<Record<string, readonly string[]>>
|
||||
|
||||
/** Complete configurable input to package dependency classification. */
|
||||
export interface PackageDependencyPolicy {
|
||||
readonly clientFaceInclude: readonly string[]
|
||||
readonly clientFaceExclude: readonly string[]
|
||||
readonly hostPackages: readonly string[]
|
||||
readonly safeHostDependencyExports: HostDependencyExports
|
||||
readonly peerRequiredHostExports: HostDependencyExports
|
||||
}
|
||||
|
||||
/** Repository dependency policy consumed by verification and benchmarking. */
|
||||
|
|
@ -26,6 +56,8 @@ export const PACKAGE_DEPENDENCY_POLICY: PackageDependencyPolicy = {
|
|||
clientFaceInclude: CLIENT_FACE_INCLUDE,
|
||||
clientFaceExclude: CLIENT_FACE_EXCLUDE,
|
||||
hostPackages: HOST_DEPENDENCY_PACKAGES,
|
||||
safeHostDependencyExports: SAFE_HOST_DEPENDENCY_EXPORTS,
|
||||
peerRequiredHostExports: PEER_REQUIRED_HOST_EXPORTS,
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue