Yichen Jiang 3 тижнів тому
батько
коміт
743df72db2

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.i18n.yaml

@@ -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/architecture/2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.md
-2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.md: 70e2e1b41a5642e348fc710752861b89d1de66ba
-2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.zh.md: 58209202586c3939f929afd16f4d0a275ef3bb96
+2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.md: 0c83dc5e23515708d78747b1489847ac4f229705
+2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.zh.md: badb759753229def9e29c8b706b1dab03a798bb5

+ 2 - 0
.agents/notes/implemented/architecture/2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.md

@@ -22,6 +22,8 @@ Third-party plugin failures must leave the application’s management endpoints
 
 **Unhandled process failures stay fatal.** `installFailLoud` remains until shutdown. Only duplicate rejections already observed by an entry audit are coalesced through its process checkpoint. Unrelated detached rejections retain master’s teardown-and-exit policy. No in-process group protects against `process.exit`, a blocked event loop, native crashes or OOM. Preset generation owners retain their own strict mount-and-cleanup behavior.
 
+**Public helpers serve launcher and management operations.** Composition, conflict checks, diagnostics and package management expose their inputs and results. Per-row diagnostic helpers and intermediate bundle analysis stay package-private so callers depend on operation results rather than analysis steps. Ownership analysis returns row owners and conflicts without retaining unused per-bundle results.
+
 ## Alternatives considered
 
 **Keep contained groups and child probes.** Contained groups protected against transactional Loader rollback and supplied failure records for removed rows. Native entries retain both siblings and failed rows, so wrapping adds identities without supplying bundle-wide enablement that layer composition lacks. Child probes bounded discovery-time execution and discovered undeclared exports and schemas. Static declarations avoid that execution; automatic main-export detection and pre-mount schemas are given up. A future execution sandbox needs its own process ownership and teardown design, not a discovery probe presented as runtime isolation.

+ 2 - 0
.agents/notes/implemented/architecture/2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.zh.md

@@ -22,6 +22,8 @@ Status: implemented
 
 **未处理的进程级失败仍然致命。** `installFailLoud` 保持到应用关闭。只有条目审计已观测到的重复 rejection 会在其进程检查点内合并。无关的脱离管理 rejection 保留 master 的清理退出策略。进程内的组无法防护 `process.exit`、事件循环阻塞、原生崩溃或 OOM。预设代际的拥有者保留独立的严格挂载与清理行为。
 
+**公开 helper 服务于启动与管理操作。** 组合、冲突检查、诊断和包管理公开其输入与结果。单行诊断 helper 和组合包分析中间数据保留在包内,使调用方依赖操作结果而非分析步骤。归属分析返回行归属与冲突,不保留无人使用的逐包分析结果。
+
 ## Alternatives considered
 
 **保留 contained group 和子进程 probe。** contained group 防止事务式 Loader 回滚,并为已移除行提供失败记录。原生条目会同时保留成功行与失败行,包装只增加身份,并未提供整层组合所缺少的整包启停能力。子进程 probe 为发现阶段的执行设定边界,并发现未声明导出与 schema。静态声明避免这次执行,同时放弃自动主入口识别和挂载前 schema。未来的执行沙箱需要独立设计进程归属与清理,不能把发现 probe 视为运行隔离。

+ 2 - 2
docs/architecture.i18n.yaml

@@ -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 docs/architecture.md
-architecture.md: 6cd6c45142f0a743e0c4a7b5104ec3b15fcf706a
-architecture.zh.md: 6add99a4086f7d3ec76e5e2cbbb9a1a2a3891176
+architecture.md: 8ac3ceb3e3daa298a394d05b1600355e62ed628a
+architecture.zh.md: c165ed29ed983e1c2d6f2c9f31a01ca38d96ae92

+ 1 - 1
docs/architecture.md

@@ -26,7 +26,7 @@ Each declares itself in its own `package.json` under a `dsh` field: `dsh.profile
 
 Layers apply to an empty entry list in this order: each bundle in the profile's listed order, then the profile's `cordis.patch.yml`, then the home-level one, then any `--patch` overlay. A patch targets a row by id and replaces its whole config, or inserts new rows.
 
-An installed bundle is external: its rows mount in one contained group, failing rows are isolated and reported, and the `plugins` Remote changes profiles at runtime ([app-boot](../packages/boot/app-boot/README.md), [plugin-manager](../packages/host/plugin-manager/README.md)).
+Bundle rows retain declared ids and group hierarchy; enablement selects whole patch layers. Required entries must activate at startup; other row failures produce warnings ([app-boot](../packages/boot/app-boot/README.md)). The `plugins` Remote manages profile packages and rows ([plugin-manager](../packages/host/plugin-manager/README.md)).
 
 Custom profiles default to live patch reload. The shipped `web` profile is live; `headless`, `sdk`, `sdk-minimal`, and `acp` apply all layers once at startup because replacing a one-shot or stdio application's dependencies after it owns work would invalidate that lifecycle.
 

+ 1 - 1
docs/architecture.zh.md

@@ -26,7 +26,7 @@
 
 各层按此顺序应用在空条目列表之上:先按 profile 列出的顺序应用每个组合包,然后是 profile 的 `cordis.patch.yml`,然后是 home 级的那份,最后是任意 `--patch` overlay。一条 patch 按 id 定位某个条目并替换其整个 config,或插入新条目。
 
-已安装的组合包是外部的:它的行挂在一个受控组下,失败的行被隔离并报告,`plugins` Remote 在运行时修改 profile([app-boot](../packages/boot/app-boot/README.zh.md)、[plugin-manager](../packages/host/plugin-manager/README.zh.md))。
+组合包的行保留声明的 id 和组层级;启停选择整个 patch 层。启动时必需条目必须激活,其他行的失败产生警告([app-boot](../packages/boot/app-boot/README.zh.md))。`plugins` Remote 管理 profile 的插件包与插件行([plugin-manager](../packages/host/plugin-manager/README.zh.md))。
 
 自定义 profile 默认实时重载 patch。随附的 `web` profile 使用实时重载;`headless`、`sdk`、`sdk-minimal` 和 `acp` 则只在启动时应用一次所有配置层,因为一次性应用或 stdio 应用拥有工作之后,替换其依赖会破坏该生命周期。
 

+ 2 - 2
packages/boot/app-boot/README.i18n.yaml

@@ -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: c03e33ef796f593f3cd260df238845f738d6beea
-README.zh.md: 62692813cc4c80f6b04e5a8280cd86110f268e73
+README.md: 94e46a03f5bd7603e8a5444e2663b937f028e65f
+README.zh.md: 516128a34ab8df8a96a4a9b7bfa5b12dba1b9112

+ 1 - 1
packages/boot/app-boot/README.md

@@ -142,7 +142,7 @@ This section explains how the outcomes above are realized and points at the code
 
 ### Helper behavior
 
-The exports each own one stage of the boot: config resolution and snapshot replay, layered environment loading, fail-loud reporting, activation auditing, patch parsing, root-include mounting, config dump rendering, live patch watching, profile composition, and the harness-source section. Per-export contracts live in the code, not this README — see [`src/index.ts`](src/index.ts) and [`src/profile.ts`](src/profile.ts).
+The package root exports boot, composition, diagnostics and package-management operations with their input and result types. Per-row diagnostic helpers and intermediate bundle analysis stay internal. Per-export contracts live in [`src/index.ts`](src/index.ts) and [`src/profile.ts`](src/profile.ts).
 
 ### Source map
 

+ 1 - 1
packages/boot/app-boot/README.zh.md

@@ -142,7 +142,7 @@ Loader 结算后,app-boot 将 optional 失败报告为警告;若已启用的
 
 ### Helper 行为
 
-每个导出各负责启动的一个阶段:配置解析与快照回放、分层环境加载、明确报错的保护机制、激活审计、patch 解析、根 include 挂载、配置 dump 渲染、活动 patch 监视、profile 组合,以及 harness 源码段落。各导出的约定在代码中,不在本 README——见 [`src/index.ts`](src/index.ts) 与 [`src/profile.ts`](src/profile.ts)。
+包根导出启动、组合、诊断和包管理操作及其输入与结果类型。单行诊断 helper 和组合包分析中间数据保留在包内。各导出的约定见 [`src/index.ts`](src/index.ts) 与 [`src/profile.ts`](src/profile.ts)。
 
 ### 源码地图
 

+ 4 - 8
packages/boot/app-boot/src/compose-stack.ts

@@ -7,7 +7,7 @@
 
 import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
 import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
-import { analyzeBundleLayer, type AnalyzedBundleLayer } from './external-bundles.ts'
+import { analyzeBundleLayer } from './external-bundles.ts'
 import { visitPatchRows, visitRowTree } from './patch-rows.ts'
 import type { ProfileLayer } from './profile.ts'
 
@@ -58,14 +58,12 @@ export interface ComposedStack {
   readonly userDisabledRowIds: ReadonlySet<string>
 }
 
-/** Row-id ownership across the bundle layers: who owns each id, which bundles lost, and how the rest mount. */
+/** Row-id ownership and conflicts across the bundle layers. */
 export interface LayerOwnership {
   /** The layer that owns each id a bundle layer introduces. */
   readonly owners: Map<string, ProfileLayer>
   /** The conflicts of each bundle left out, by package name. */
   readonly skipped: Map<string, RowConflict[]>
-  /** The analysis of each bundle layer that owns its ids, by package name; rendered once and mounted as is. */
-  readonly composed: Map<string, AnalyzedBundleLayer>
 }
 
 /** One conflict with its message: the id's other declarer, or the losing layer itself declaring it twice. */
@@ -91,12 +89,11 @@ function rowIds(row: EntryOptions): string[] {
  * an earlier layer's id is omitted whole. Restating a child in the same group
  * preserves its ownership; moving it to another group is a duplicate.
  * @param layers - the profile's bundle layers, in manifest order.
- * @returns row owners, rejected-bundle conflicts, and each accepted layer's analysis.
+ * @returns row owners and rejected-bundle conflicts.
  */
 export function claimLayerIds(layers: readonly ProfileLayer[]): LayerOwnership {
   const owners = new Map<string, ProfileLayer>()
   const skipped = new Map<string, RowConflict[]>()
-  const composed = new Map<string, AnalyzedBundleLayer>()
   for (const layer of layers) {
     const { packageName } = layer
     const composition = analyzeBundleLayer(layer)
@@ -114,9 +111,8 @@ export function claimLayerIds(layers: readonly ProfileLayer[]): LayerOwnership {
       continue
     }
     for (const id of composition.rows.keys()) owners.set(id, layer)
-    composed.set(packageName, composition)
   }
-  return { owners, skipped, composed }
+  return { owners, skipped }
 }
 
 /**

+ 5 - 11
packages/boot/app-boot/src/external-bundles.ts

@@ -1,7 +1,6 @@
 /** Bundle patch ownership and the profile manifest's installed/enabled layer lists. */
 
 import { join } from 'node:path'
-import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
 import type { DshProfileManifest } from '@deepseek-ai/dsh-package-manifest'
 import { visitIdentifiedRows } from './patch-rows.ts'
 import {
@@ -9,23 +8,21 @@ import {
 } from './profile.ts'
 
 /** One row declared twice within a bundle layer. */
-export interface DuplicateRow {
+interface DuplicateRow {
   readonly rowId: string
   readonly moduleName: string
 }
 
-/** Static row ownership and overrides of one unmodified bundle patch list. */
-export interface AnalyzedBundleLayer {
-  readonly patches: PatchOptions[]
+/** Declared row ids and duplicates within one bundle patch list. */
+interface AnalyzedBundleLayer {
   readonly rows: Map<string, string>
   readonly duplicates: DuplicateRow[]
-  readonly overrides: string[]
 }
 
 /**
  * Inspect the rows a layer introduces without changing their ids or parents.
  * @param layer - the bundle patch list.
- * @returns its declared rows, duplicates, and external override targets.
+ * @returns its declared rows and duplicates.
  */
 export function analyzeBundleLayer(layer: ProfileLayer): AnalyzedBundleLayer {
   const rows = new Map<string, string>()
@@ -41,10 +38,7 @@ export function analyzeBundleLayer(layer: ProfileLayer): AnalyzedBundleLayer {
       declaredUnder.set(id, place.target)
     }
   })
-  const overrides = layer.patches.flatMap(patch => (
-    patch.insert === undefined && patch.id !== undefined && !rows.has(patch.id) ? [patch.id] : []
-  ))
-  return { patches: layer.patches, rows, duplicates, overrides }
+  return { rows, duplicates }
 }
 
 /**

+ 2 - 2
packages/boot/app-boot/src/index.ts

@@ -52,10 +52,10 @@ export {
   type ProfileModuleFallbackOptions,
   type ProfileTemplate,
 } from './profile.ts'
-export { entryIssue, inspectEntryIssues, type EntryIssue } from './entry-issues.ts'
+export { inspectEntryIssues, type EntryIssue } from './entry-issues.ts'
 export {
   disableBundle, enableBundle, reconcileInstalledBundles,
-  type BundleReconciliation, type AnalyzedBundleLayer, type DuplicateRow,
+  type BundleReconciliation,
 } from './external-bundles.ts'
 export {
   claimLayerIds, composeProfileStack, formatRowConflict,

+ 3 - 6
packages/boot/app-boot/tests/compose-stack.spec.ts

@@ -110,22 +110,19 @@ describe('claimLayerIds', () => {
       { insert: [{ id: 'self-group', name: 'cordis:group', group: true, config: [{ id: 'row', name: 'self/row' }] }] },
       { id: 'self-group', config: [{ id: 'row', name: 'self/row' }, { id: 'row', name: 'self/row-again' }] },
     ])
-    const { skipped, composed } = claimLayerIds([base, self])
+    const { skipped } = claimLayerIds([base, self])
     expect(skipped.get('self')?.map(conflict => conflict.message)).toEqual(['row "row" is declared twice by self'])
-    expect(composed.has('self')).toBe(false)
   })
 
-  it('leaves out a bundle that declares one of its own ids twice and composes each mounted bundle once', () => {
+  it('leaves out a bundle that declares one of its own ids twice while accepting other bundles', () => {
     const stutter = layer('stutter', [{ insert: [{ id: 'x', name: 'stutter/a' }, { id: 'x', name: 'stutter/b' }] }])
     const clean = layer('clean', [{ insert: [{ id: 'y', name: 'clean' }] }])
-    const { owners, skipped, composed } = claimLayerIds([base, stutter, clean])
+    const { owners, skipped } = claimLayerIds([base, stutter, clean])
     expect(skipped.get('stutter')).toEqual([
       { rowId: 'x', moduleName: 'stutter/b', layer: 'stutter', packageName: 'stutter', declaredBy: 'stutter', message: 'row "x" is declared twice by stutter' },
     ])
     expect(owners.has('x')).toBe(false)
     expect(owners.get('y')?.packageName).toBe('clean')
-    expect([...composed.keys()]).toEqual(['@deepseek-ai/dsh-base', 'clean'])
-    expect(composed.get('clean')?.patches[0]).toEqual({ insert: [{ id: 'y', name: 'clean' }] })
   })
 
   it('gives the earlier external bundle the id and leaves the later one out whole', () => {

+ 2 - 1
packages/boot/app-boot/tests/entry-issues.spec.ts

@@ -7,7 +7,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
 import { Context, type Plugin } from '@deepseek-ai/cordis'
 import Loader, { type EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
 import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
-import { boot, composeProfileStack, entryIssue, inspectEntryIssues, ProfileRuntime, rootIncludeEntry, warnNestedFiberFailures, type Profile, type ProfileLayer } from '../src/index.ts'
+import { boot, composeProfileStack, inspectEntryIssues, ProfileRuntime, rootIncludeEntry, warnNestedFiberFailures, type Profile, type ProfileLayer } from '../src/index.ts'
+import { entryIssue } from '../src/entry-issues.ts'
 
 const roots: string[] = []
 const contexts: Context[] = []

+ 1 - 4
packages/boot/app-boot/tests/external-bundles.spec.ts

@@ -18,7 +18,7 @@ function layer(patches: PatchOptions[]): ProfileLayer {
   return { packageName: 'ext', version: undefined, packageDir: '/nowhere', patchPath: '/nowhere/patch.yml', patches }
 }
 describe('analyzeBundleLayer', () => {
-  it('preserves parents, explicit ids and overrides across several groups', () => {
+  it('identifies declared rows across several groups without claiming override targets', () => {
     const patches: PatchOptions[] = [
       { insert: [{ id: 'own', name: 'cordis:group', group: true, config: [{ id: 'child', name: 'ext/child' }] }] },
       { id: 'tools', insert: [{ id: 'tool', name: 'ext/tool' }] },
@@ -27,16 +27,13 @@ describe('analyzeBundleLayer', () => {
       { id: 'child', disabled: true },
     ]
     const result = analyzeBundleLayer(layer(patches))
-    expect(result.patches).toBe(patches)
     expect([...result.rows.keys()]).toEqual(['own', 'child', 'tool', 'panel'])
-    expect(result.overrides).toEqual(['webserver'])
     expect(result.duplicates).toEqual([])
   })
   it('keeps array-valued plugin configuration out of the row inventory', () => {
     const result = analyzeBundleLayer(layer([{ id: 'provider', config: ['value', { value: 42 }, null] }]))
     expect([...result.rows]).toEqual([])
     expect(result.duplicates).toEqual([])
-    expect(result.overrides).toEqual(['provider'])
   })
   it('rejects repeated insert ids but permits restating children in the same group', () => {
     const patches: PatchOptions[] = [