refactor(boot): finish the interception vocabulary in code comments and the app-boot README
The Consumer-owned strictness bullet names the Loader subtree that agent presets own instead of the runtime resolution; the service field, its file comment, the dispose JSDoc, and the route JSDoc use interception; the native-after-interception route documents both anchors it can carry; stale module-fallback comments name the runtime resolution; importFromResolution matches its parameter.
This commit is contained in:
1 parent
b605d114bd
commit
f7f22a6b48
11 files changed
+35
-35
No files matched your search
@@ -173,7 +173,7 @@ export function prepareProfile(name: string, userLayer = true, fromDefaultProfil
|
||||
/** One profile's patch layers, in application order. */
|
||||
interface ComposedProfile {
|
||||
profile: Profile
|
||||
/** Immutable package fallback selected before any plugin imports. */
|
||||
/** Immutable runtime resolution computed before any plugin imports. */
|
||||
resolution: RuntimeResolution
|
||||
/** Command-line overlay contents, frozen for this invocation. */
|
||||
overlays: PatchOptions[]
|
||||
|
||||
@@ -35,7 +35,7 @@ const BASE_PATCH = join(REPO_ROOT, 'packages/bundle/base/cordis.patch.yml')
|
||||
const WEB_PATCH = join(REPO_ROOT, 'packages/bundle/web-app/cordis.patch.yml')
|
||||
const CODEX_PACKAGE_DIR = join(REPO_ROOT, 'packages/subagent/subagent-codex')
|
||||
const CLAUDE_CODE_PACKAGE_DIR = join(REPO_ROOT, 'packages/subagent/subagent-claude-code')
|
||||
/** The installation anchor whose dependency surface the preset module fallback mirrors. */
|
||||
/** The installation anchor whose dependency surface the runtime resolution mirrors. */
|
||||
const INSTALL_ANCHOR = join(REPO_ROOT, 'apps/cli/package.json')
|
||||
const MINIMAL_PROMPT = 'You are a helpful software engineer assistant.'
|
||||
const MINIMAL_BASH_DESCRIPTION = `Run commands in a bash shell
|
||||
|
||||
@@ -196,7 +196,7 @@ export function recordedSessionFixturePath(path: string, version: number): strin
|
||||
/** The shipped composition under test: the dsh-base and dsh-web-app bundle patches over the empty profile root. */
|
||||
const BASE_PATCH_PATH = join(REPO_ROOT, 'packages/bundle/base/cordis.patch.yml')
|
||||
const WEB_PATCH_PATH = join(REPO_ROOT, 'packages/bundle/web-app/cordis.patch.yml')
|
||||
/** The installation anchor whose dependency surface the profile module fallback mirrors. */
|
||||
/** The installation anchor whose dependency surface the runtime resolution mirrors. */
|
||||
const INSTALL_ANCHOR = join(REPO_ROOT, 'apps/cli/package.json')
|
||||
|
||||
// Replay publishes the provider catalog the gateway routes to (providers
|
||||
|
||||
@@ -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 packages/boot/app-boot/README.md
|
||||
README.md: d5dc26c4ed2220bf07edb5c8494a0dec067a6adb
|
||||
README.zh.md: b54e67c84c060afea5fb3f9585e91b7bcc12e4a2
|
||||
README.md: fed6f6833dd22254270b86beccd797d3b6054d7f
|
||||
README.zh.md: bedf8ae3d019607cbf04e24a38febd3441e06bbd
|
||||
@@ -112,7 +112,7 @@ This section explains how the outcomes above are realized and points at the code
|
||||
- **Profile launch data.** `ctx.profileContext` contains only profile locations, startup bundle names, parsed invocation overlays and the telemetry opt-out value. `readProfilePatches()` composes the supplied startup profile or reads current files at those locations; callers schedule and apply the result.
|
||||
- **Process-local module resolution.** The launcher installs the runtime resolution as an interception on Node's internal ESM and CommonJS resolvers before profile rows mount. Node still owns exports, conditions, subpaths, module caches, and error codes; routed ESM failures report the original importer instead of the internal lookup anchor. `ctx.pluginPackages` exposes package metadata from the same runtime resolution without recording Entry imports; an installed runtime resolution is authoritative even for a miss, while low-level embedders that install the service without one retain native lookup.
|
||||
- **Two Loader builtins.** `mountRootInclude` registers `cordis:include` and `cordis:group` as Loader builtins: a group row gives one `isolate` realm to a provider and its consumers together, and an agent preset outside this workspace cannot resolve `@deepseek-ai/cordis-plugin-group` by name. Both load through the ambient module pipeline rather than the included tree's own specifier resolution.
|
||||
- **Consumer-owned strictness.** Ordinary Loader groups keep successful siblings. App-boot applies the global required-entry policy after initial settlement; agent presets and dynamic multi-entry compositions own and dispose their separate runtime resolution when they require all-or-nothing setup. App-boot reads failed fibers to report their recorded errors and coalesces duplicate Loader rejection notifications through one process checkpoint.
|
||||
- **Consumer-owned strictness.** Ordinary Loader groups keep successful siblings. App-boot applies the global required-entry policy after initial settlement; agent presets and dynamic multi-entry compositions own and dispose their separate Loader subtree when they require all-or-nothing setup. App-boot reads failed fibers to report their recorded errors and coalesces duplicate Loader rejection notifications through one process checkpoint.
|
||||
- **One runtime resolution.** The installation-first and ordered-bundle breadth-first traversal produces the runtime table. Runtime resolution creates no links; runtime resolution entries occupy their package names at `$DSH_HOME/profiles/node_modules`, and every other name sees that directory as an ordinary ancestor. Profile load removes the `.dsh-module-fallback` projections that link-backend releases wrote into a profile; pnpm-installed packages stay. External bare targets selected by package `imports` use the same package order, while Node retains mapping, conditions, and exact target resolution. A complete successor may add package names atomically, while changing or removing an existing mapping requires restart.
|
||||
- **Application-owned profiles.** Application-owned profiles use the same runtime resolution. Resolution does not modify their `node_modules`; pnpm owns installed packages.
|
||||
- **Owned Workers.** Worker build banners import `@deepseek-ai/dsh-app-boot/worker/profile-resolution-bootstrap` before bundled business code. Each Worker installs the structured-cloned runtime resolution in its own isolate. The bootstrap bundle has no static package imports. Source Worker entries retain their self-contained dependency closure, and third-party Workers receive no injection.
|
||||
@@ -131,7 +131,7 @@ The exports each own one stage of the boot: config resolution and snapshot repla
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| [`src/index.ts`](src/index.ts) | Boot helpers: config resolution, environment loading, fail-loud guard, activation audit, patch parsing, config dump, harness-source section |
|
||||
| [`src/profile.ts`](src/profile.ts) | Profile discovery, initialization, bundle resolution, module fallback |
|
||||
| [`src/profile.ts`](src/profile.ts) | Profile discovery, initialization, bundle resolution, runtime resolution construction |
|
||||
| [`src/profile-plugins.ts`](src/profile-plugins.ts) | Installed dependencies, bundle activation policy, and manifest updates |
|
||||
| [`src/profile-sanitize.ts`](src/profile-sanitize.ts) | Profile patch backup and recovery bundle activation |
|
||||
| [`src/profile-resolution/`](src/profile-resolution/) | Runtime resolver, package-metadata service, and built Worker bootstrap |
|
||||
|
||||
@@ -112,7 +112,7 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如
|
||||
- **Profile 启动数据。** `ctx.profileContext` 只包含 profile 位置、启动时组合包名称、已解析的调用级 overlay 与遥测退出值。`readProfilePatches()` 组合传入的启动 profile,或读取这些位置上的当前文件;调用方负责调度和应用结果。
|
||||
- **进程内模块解析。** runtime 解析会在挂载 profile 条目前,将 runtime resolution 作为拦截安装到 Node 的 ESM 与 CommonJS 内部 resolver。exports、conditions、subpath、模块缓存和错误码仍由 Node 负责;路由后的 ESM 失败会报告原始 importer,而不是内部查找锚点。`ctx.pluginPackages` 从同一 runtime resolution 提供 package metadata,不记录 Entry import;安装 runtime resolution 后,即使查询未命中也以 runtime resolution 为准,仅安装服务而未提供 runtime resolution 的底层嵌入方仍使用 Node 原生查找。
|
||||
- **两个 Loader builtin。** `mountRootInclude` 把 `cordis:include` 与 `cordis:group` 注册为 Loader builtin:group 行能把一个提供方与它的消费方放进同一个 `isolate` realm,而位于本工作区之外的 agent preset 无法按名称解析 `@deepseek-ai/cordis-plugin-group`。两者都通过宿主的模块管线加载,而非被包含树自身的说明符解析。
|
||||
- **由 consumer 持有严格语义。** 普通 Loader group 保留成功 sibling。App-boot 在首次结算后应用全局 required-entry policy;agent preset 与动态多 entry 组合在需要 all-or-nothing setup 时,持有并拆卸各自的独立 runtime resolution。App-boot 读取 failed fiber 来报告已记录的错误,并在一个进程检查点内合并 Loader 重复的 rejection 通知。
|
||||
- **由 consumer 持有严格语义。** 普通 Loader group 保留成功 sibling。App-boot 在首次结算后应用全局 required-entry policy;agent preset 与动态多 entry 组合在需要 all-or-nothing setup 时,持有并拆卸各自的独立 Loader 子树。App-boot 读取 failed fiber 来报告已记录的错误,并在一个进程检查点内合并 Loader 重复的 rejection 通知。
|
||||
- **唯一 runtime resolution。** 安装优先、有序 bundle 逐根 breadth-first 遍历生成运行时表。runtime 解析不创建链接;runtime resolution 条目占据 `$DSH_HOME/profiles/node_modules` 上各自的包名位置,其余包名把该目录当作普通祖先。profile 加载时删除 Link 后端发布版写进 profile 的 `.dsh-module-fallback` 投影;pnpm 安装的包保留。package `imports` 选中的外部 bare target 使用相同的选包顺序,映射、conditions 和精确 target 解析仍由 Node 负责。完整后继 runtime resolution 可以原子增加 package name,修改或删除既有映射则要求重启。
|
||||
- **应用自有 profile。** 应用自有 profile 使用相同的 runtime resolution。解析过程不修改其 `node_modules`;已安装包由 pnpm 管理。
|
||||
- **自有 Worker。** Worker 构建 banner 会在业务 bundle 前导入 `@deepseek-ai/dsh-app-boot/worker/profile-resolution-bootstrap`。每个 Worker 在自己的 isolate 中安装结构化克隆的 runtime resolution。bootstrap bundle 不静态导入任何包。源码 Worker 入口保留自包含依赖,第三方 Worker 不接受注入。
|
||||
@@ -131,7 +131,7 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| [`src/index.ts`](src/index.ts) | 启动 helper:配置解析、环境加载、会明确报错的保护机制、激活审计、patch 解析、配置 dump、harness 源码段落 |
|
||||
| [`src/profile.ts`](src/profile.ts) | profile 发现、初始化、组合包解析、模块后备机制 |
|
||||
| [`src/profile.ts`](src/profile.ts) | profile 发现、初始化、组合包解析、runtime resolution 构造 |
|
||||
| [`src/profile-plugins.ts`](src/profile-plugins.ts) | 已安装依赖、bundle 启用策略与 manifest 更新 |
|
||||
| [`src/profile-sanitize.ts`](src/profile-sanitize.ts) | profile patch 备份与恢复 bundle 启用状态 |
|
||||
| [`src/profile-resolution/`](src/profile-resolution/) | 运行时 resolver、package metadata 服务与构建后 Worker bootstrap |
|
||||
|
||||
@@ -51,8 +51,8 @@ type EsmResolve = (
|
||||
* Where Node continues a scoped bare request. `native` keeps the importer's own lookup: the chain is
|
||||
* answered below the interception layer. `interception` answers at the interception layer from the
|
||||
* entry's declaring manifest; `after` anchors Node's chain above the layer for a CommonJS subpath the
|
||||
* entry lacks. `native-after-interception` has no entry: Node's chain resumes at the interception layer,
|
||||
* anchored by `parent`.
|
||||
* entry lacks. `native-after-interception` resumes Node's chain from `parent`: the interception layer for a
|
||||
* name without an entry, or the layer above it after an `interception` subpath miss.
|
||||
*/
|
||||
type ResolutionRoute =
|
||||
| { readonly kind: 'interception'; readonly entry: RuntimeResolutionEntry; readonly after: string }
|
||||
@@ -105,7 +105,7 @@ export interface RuntimeInterception {
|
||||
* @throws when the profile scope or an existing package mapping changes.
|
||||
*/
|
||||
replace(successor: RuntimeResolution): void
|
||||
/** Restore the native resolver methods. Registrations dispose in reverse order. */
|
||||
/** Restore the native resolver methods. Interceptions dispose in reverse order. */
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
@@ -695,7 +695,7 @@ export function installRuntimeInterception(
|
||||
)
|
||||
loader.resolveSync = wrapped
|
||||
restoreEsm = () => {
|
||||
/* v8 ignore else -- registrations are disposed in reverse installation order */
|
||||
/* v8 ignore else -- interceptions are disposed in reverse installation order */
|
||||
if (loader.resolveSync === wrapped) loader.resolveSync = original
|
||||
}
|
||||
} else {
|
||||
@@ -865,7 +865,7 @@ export function installRuntimeInterception(
|
||||
},
|
||||
replace(next) { router.replace(next) },
|
||||
dispose() {
|
||||
/* v8 ignore else -- registrations are disposed in reverse installation order */
|
||||
/* v8 ignore else -- interceptions are disposed in reverse installation order */
|
||||
if (cjs._resolveFilename === wrappedFilename) cjs._resolveFilename = originalFilename
|
||||
restoreEsm()
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Package metadata resolved through one profile resolution registration. */
|
||||
/** Package metadata resolved through one runtime interception. */
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
@@ -57,18 +57,18 @@ function readPackage(dir: string, fallbackName: string): PluginPackage | undefin
|
||||
/** Package lookup shared by metadata consumers in one profile process. */
|
||||
export class PluginPackages extends Service {
|
||||
private packages = new Map<string, PluginPackage | undefined>()
|
||||
private readonly resolver: RuntimeInterception | undefined
|
||||
private readonly interception: RuntimeInterception | undefined
|
||||
private disposeWorkerResolution: (() => void) | undefined
|
||||
|
||||
constructor(ctx: Context, config: PluginPackagesConfig = {}) {
|
||||
super(ctx, 'pluginPackages')
|
||||
if (config.resolution === undefined) return
|
||||
const resolver = installRuntimeInterception(config.resolution)
|
||||
const interception = installRuntimeInterception(config.resolution)
|
||||
this.disposeWorkerResolution = registerWorkerResolution(config.resolution)
|
||||
this.resolver = resolver
|
||||
this.interception = interception
|
||||
ctx.effect(() => () => {
|
||||
this.disposeWorkerResolution?.()
|
||||
resolver.dispose()
|
||||
interception.dispose()
|
||||
}, 'profile package resolution')
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ export class PluginPackages extends Service {
|
||||
* @param successor - fully constructed successor generation.
|
||||
*/
|
||||
replace(successor: RuntimeResolution): void {
|
||||
if (this.resolver === undefined) throw new Error('plugin-packages: runtime resolution is not installed')
|
||||
this.resolver.replace(successor)
|
||||
if (this.interception === undefined) throw new Error('plugin-packages: runtime resolution is not installed')
|
||||
this.interception.replace(successor)
|
||||
this.packages = new Map()
|
||||
this.disposeWorkerResolution?.()
|
||||
this.disposeWorkerResolution = registerWorkerResolution(successor)
|
||||
@@ -93,9 +93,9 @@ export class PluginPackages extends Service {
|
||||
packageOf(specifier: string, parentURL: string): PluginPackage | undefined {
|
||||
const name = barePackageName(specifier)
|
||||
if (name === undefined) return undefined
|
||||
const dir = this.resolver === undefined
|
||||
const dir = this.interception === undefined
|
||||
? packageDirFromParent(name, parentURL)
|
||||
: this.resolver.packageDir(name, parentURL)
|
||||
: this.interception.packageDir(name, parentURL)
|
||||
if (dir === undefined) return undefined
|
||||
const key = JSON.stringify({ dir, name })
|
||||
if (!this.packages.has(key)) this.packages.set(key, readPackage(dir, name))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Package metadata queries share the active profile resolution resolution. */
|
||||
/** Package metadata queries share the active runtime resolution. */
|
||||
|
||||
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
@@ -92,7 +92,7 @@ function stageProfile(home: string, name: string, bundleAnchor: string): Profile
|
||||
}
|
||||
}
|
||||
|
||||
async function importFromGeneration(
|
||||
async function importFromResolution(
|
||||
resolution: RuntimeResolution, specifier: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
const addon = createRequire(import.meta.url)('node-addon-require-builtin') as {
|
||||
@@ -584,8 +584,8 @@ describe('createRuntimeResolution', () => {
|
||||
writeFileSync(join(nestedDir, 'index.js'), 'export const nested = "selected"\n')
|
||||
const home = tmp()
|
||||
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
|
||||
await expect(importFromGeneration(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
await expect(importFromGeneration(resolution, 'nested-esm')).resolves.toMatchObject({ nested: 'selected' })
|
||||
await expect(importFromResolution(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
await expect(importFromResolution(resolution, 'nested-esm')).resolves.toMatchObject({ nested: 'selected' })
|
||||
})
|
||||
|
||||
it('resolves conditional subpath exports through Node', async () => {
|
||||
@@ -604,8 +604,8 @@ describe('createRuntimeResolution', () => {
|
||||
writeFileSync(join(bundleDir, 'dist', 'web', 'index.mjs'), 'export const web = true\n')
|
||||
const home = tmp()
|
||||
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
|
||||
await expect(importFromGeneration(resolution, 'bundle-a/mini')).resolves.toMatchObject({ mini: true })
|
||||
await expect(importFromGeneration(resolution, 'bundle-a/web')).resolves.toMatchObject({ web: true })
|
||||
await expect(importFromResolution(resolution, 'bundle-a/mini')).resolves.toMatchObject({ mini: true })
|
||||
await expect(importFromResolution(resolution, 'bundle-a/web')).resolves.toMatchObject({ web: true })
|
||||
})
|
||||
|
||||
it('uses the legacy index fallback when a package has no exports or main', async () => {
|
||||
@@ -616,7 +616,7 @@ describe('createRuntimeResolution', () => {
|
||||
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(manifest))
|
||||
const home = tmp()
|
||||
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
|
||||
await expect(importFromGeneration(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
await expect(importFromResolution(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
})
|
||||
|
||||
it('uses Node legacy resolution for an extensionless main entry', async () => {
|
||||
@@ -627,7 +627,7 @@ describe('createRuntimeResolution', () => {
|
||||
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(manifest))
|
||||
const home = tmp()
|
||||
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
|
||||
await expect(importFromGeneration(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
await expect(importFromResolution(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
})
|
||||
|
||||
it('fails loud on a missing legacy main entry', async () => {
|
||||
@@ -638,7 +638,7 @@ describe('createRuntimeResolution', () => {
|
||||
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(manifest))
|
||||
rmSync(join(bundleDir, 'index.js'))
|
||||
const resolution = await createRuntimeResolution({ installAnchor: anchor, home: tmp() })
|
||||
await expect(importFromGeneration(resolution, 'bundle-a')).rejects.toMatchObject({ code: 'ERR_MODULE_NOT_FOUND' })
|
||||
await expect(importFromResolution(resolution, 'bundle-a')).rejects.toMatchObject({ code: 'ERR_MODULE_NOT_FOUND' })
|
||||
})
|
||||
|
||||
it('preserves native ESM export errors and null-map legacy resolution', async () => {
|
||||
@@ -658,14 +658,14 @@ describe('createRuntimeResolution', () => {
|
||||
const home = tmp()
|
||||
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
|
||||
if (mode === 'absent-map') {
|
||||
await expect(importFromGeneration(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
await expect(importFromResolution(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
} else {
|
||||
const specifier = mode === 'null-subpath' ? 'bundle-a/bad' : 'bundle-a'
|
||||
const code = mode === 'missing' ? 'ERR_MODULE_NOT_FOUND'
|
||||
: mode === 'directory' ? 'ERR_UNSUPPORTED_DIR_IMPORT'
|
||||
: mode === 'null' || mode === 'null-subpath' ? 'ERR_PACKAGE_PATH_NOT_EXPORTED'
|
||||
: 'ERR_INVALID_PACKAGE_TARGET'
|
||||
await expect(importFromGeneration(resolution, specifier)).rejects.toMatchObject({ code })
|
||||
await expect(importFromResolution(resolution, specifier)).rejects.toMatchObject({ code })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -228,7 +228,7 @@ function validateAppResolution(): string[] {
|
||||
const violations: string[] = []
|
||||
const bundleManifests = bundleManifestPaths()
|
||||
// App overlays (and any config left under apps/cli/config) resolve from the
|
||||
// dsh app's own dependency surface — the profile module fallback mirrors it.
|
||||
// dsh app's own dependency surface — the runtime resolution mirrors it.
|
||||
const appManifest = readManifest('apps/cli/package.json')
|
||||
const appDependencies = {
|
||||
...appManifest.dependencies,
|
||||
|
||||
Reference in new issue
Block a user