Просмотр исходного кода

Merge branch 'worktree/pr3593-manifest-3899' into worktree/pr3594-manifest-mergeup

Yichen Jiang 1 месяц назад
Родитель
Сommit
aa1c40965c

+ 9 - 5
apps/cli/tests/built-bin.e2e.ts

@@ -1,3 +1,4 @@
+import { stageStartupBundle } from './startup-bundle.ts'
 import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
@@ -411,7 +412,8 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
       '',
     ].join('\n'))
     try {
-      const result = await runBuiltBin(['--profile', 'sdk', '--patch', patch], {
+      stageStartupBundle(home, 'sdk', patch)
+      const result = await runBuiltBin(['--profile', 'sdk'], {
         DSH_HOME: home,
         DSH_TELEMETRY_DISABLED: '1',
         DEEPSEEK_API_KEY: 'built-sdk-startup-failure-no-call',
@@ -757,18 +759,20 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
     }
   }, SPAWN_TIMEOUT_MS + 30_000)
 
-  it('keeps serving when an optional patch-overlay plugin fails', async () => {
+  it('rejects an external bundle that corrupts a builtin provider', async () => {
     const home = mkdtempSync(join(tmpdir(), 'dsh-invalid-patch-'))
     try {
-      const result = await runBuiltBin(['--profile', 'web', '--patch', invalidProvider, '--port', '0', '--no-open'], {
+      stageStartupBundle(home, 'web', invalidProvider)
+      const result = await runBuiltBin(['--profile', 'web', '--port', '0', '--no-open'], {
         DSH_HOME: home,
         DSH_BROWSER_OPEN_TEST_EXIT_ON_READY: '1',
         DEEPSEEK_API_KEY: 'keyless-invalid-config',
         DSH_TELEMETRY_DISABLED: '1',
         NODE_OPTIONS: `--import=${webReadyExitHook}`,
       })
-      expect(result.code, result.stderr).toBe(0)
-      expect(result.stdout).toMatch(/^dsh web: http:\/\/127\.0\.0\.1:\d+\/\?token=[A-Za-z0-9_-]+$/u)
+      expect(result.code, result.stderr).toBe(1)
+      expect(result.stdout).not.toContain('dsh web: http://')
+      expect(result.stderr).toContain('required startup failure')
       expect(result.stderr).toContain('llm-pi-ai')
     } finally {
       rmSync(home, { recursive: true, force: true })

+ 1 - 1
apps/cli/tests/fixtures/invalid-provider.cordis.yml

@@ -1,4 +1,4 @@
-# Invalid optional provider override used to prove best-effort startup continues.
+# Invalid override of a builtin provider: an external patch retains the target's required startup policy.
 
 - id: llm-pi-ai
   config:

+ 2 - 1
apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts

@@ -17,6 +17,7 @@ import {
   scanZstdFrames,
 } from '@deepseek-ai/dsh-session-persistence-jsonl/src/zstd.ts'
 import { describe, expect, it } from 'vitest'
+import { stageStartupBundle } from '../../../startup-bundle.ts'
 
 const goldensDir = fileURLToPath(new URL('./expected/', import.meta.url))
 const goalScenarioDir = join(goldensDir, 'goal-tools')
@@ -277,10 +278,10 @@ describe('headless stream-json snapshots', () => {
       tempDirPrefix: 'headless-snapshot-startup-error-',
       binScript: dshBinScript,
       configPath: startupFailureConfigPath,
+      prepare: (cwd) => { stageStartupBundle(join(cwd, '.dsh'), 'headless', startupFailureConfigPath) },
       binArgs: [
         '--profile', 'headless',
         '--patch', headlessOverlayPath,
-        '--patch', startupFailureConfigPath,
         'Complete the task despite the unrelated startup failure.',
       ],
       tsconfigPath,

+ 7 - 6
apps/cli/tests/profiles/web/tests/web-best-effort-startup.expected.e2e.ts

@@ -8,6 +8,7 @@ import type { Readable } from 'node:stream'
 import { fileURLToPath, pathToFileURL } from 'node:url'
 import { execa } from 'execa'
 import { describe, expect, it } from 'vitest'
+import { stageStartupBundle } from '../../../startup-bundle.ts'
 
 const repoRoot = fileURLToPath(new URL('../../../../../../', import.meta.url))
 const dshBin = join(repoRoot, 'apps/cli/lib/bin.js')
@@ -46,6 +47,7 @@ function createFixture(): Fixture {
     '}',
     '',
   ].join('\n'))
+  writeFileSync(join(root, 'schema-failure.mjs'), 'export const Config = { "~standard": { version: 1, vendor: "startup", validate() { return { issues: [{ message: "web schema failure" }] } } } }; export function apply() {}\n')
   writeFileSync(join(root, 'sync-failure.mjs'), 'export function apply() { throw new Error("web sync apply failure") }\n')
   writeFileSync(join(root, 'async-failure.mjs'), [
     'export async function apply() {',
@@ -61,10 +63,9 @@ function createFixture(): Fixture {
   ].join('\n'))
   const patch = join(root, 'failures.patch.yml')
   writeFileSync(patch, [
-    '- id: tool-todo',
-    '  disabled: false',
-    '  config: {}',
     '- insert:',
+    '    - id: web-probe-schema-failure',
+    `      name: ${pathToFileURL(join(root, 'schema-failure.mjs')).href}`,
     '    - id: web-probe-good',
     `      name: ${pathToFileURL(join(root, 'good.mjs')).href}`,
     '      config:',
@@ -134,10 +135,10 @@ async function waitForStartup(
 describe.skipIf(!builtArtifactsExist)('dsh Web profile best-effort startup', () => {
   it('serves the full Web app while unrelated entries fail to start', async () => {
     const fixture = createFixture()
+    stageStartupBundle(fixture.home, 'web', fixture.patch)
     const child = execa(process.execPath, [
       dshBin,
       '--profile', 'web',
-      '--patch', fixture.patch,
       '--no-open',
       '--port', '0',
     ], {
@@ -169,7 +170,7 @@ describe.skipIf(!builtArtifactsExist)('dsh Web profile best-effort startup', ()
       expect(html).toContain('__DSH_BOOT__')
       expect(readFileSync(fixture.events, 'utf8')).toBe('good apply\n')
       expect(startup.stderr).toContain('web-probe-import-failure')
-      expect(startup.stderr).toContain('@deepseek-ai/dsh-tool-todo')
+      expect(startup.stderr).toContain('web schema failure')
       expect(startup.stderr).toContain('web sync apply failure')
       expect(startup.stderr).toContain('web async apply failure')
       expect(startup.stderr).toContain('pending (waiting for service: webProbeMissingService)')
@@ -316,11 +317,11 @@ describe.skipIf(!builtArtifactsExist)('dsh Web profile best-effort startup', ()
       `      name: ${pathToFileURL(plugin).href}`,
       '',
     ].join('\n'))
+    stageStartupBundle(fixture.home, 'web', fixture.patch)
     try {
       const result = await execa(process.execPath, [
         dshBin,
         '--profile', 'web',
-        '--patch', fixture.patch,
         '--no-open',
         '--port', '0',
       ], {

+ 8 - 1
apps/cli/tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts

@@ -8,6 +8,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
 import { execa } from 'execa'
 import { describe, expect, it } from 'vitest'
 import { FiberState } from '@deepseek-ai/cordis'
+import { stageStartupBundle } from '../../../startup-bundle.ts'
 
 const repoRoot = fileURLToPath(new URL('../../../../../../', import.meta.url))
 const bin = join(repoRoot, 'apps/cli/lib/bin.js')
@@ -172,6 +173,8 @@ describe.skipIf(!built)('Web process failure matrix', () => {
     it.each(failures)(`${required ? 'required' : 'optional'} startup %s`, async (failure, diagnostic) => {
       const f = fixture()
       writeFileSync(f.patch, f.render(id, failure))
+      stageStartupBundle(f.home, 'web', f.patch, required ? 'boot' : 'runtime')
+      writeFileSync(f.patch, '[]\n')
       const app = start(f)
       try {
         if (required) {
@@ -360,7 +363,11 @@ describe.skipIf(!built)('Web process failure matrix', () => {
       const patch = JSON.stringify([{ insert: [f.observer, f.witness(0), {
         id: 'matrix-optional-http', name: pathToFileURL(plugin).href, config: { port: address.port },
       }] }]) + '\n'
-      if (phase === 'startup') writeFileSync(f.patch, patch)
+      if (phase === 'startup') {
+        writeFileSync(f.patch, patch)
+        stageStartupBundle(f.home, 'web', f.patch)
+        writeFileSync(f.patch, '[]\n')
+      }
       app = start(f)
       const running = app
       await app.serves()

+ 38 - 0
apps/cli/tests/startup-bundle.ts

@@ -0,0 +1,38 @@
+/** Installed bundle provenance for product-profile startup acceptance tests. */
+import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
+import { join } from 'node:path'
+import {
+  initProfile, loadOverlayPatches, PROFILE_TEMPLATES, readProfileManifest, writeProfileManifest, type ProfileLayer,
+} from '@deepseek-ai/dsh-app-boot'
+
+/**
+ * Install a patch as a real profile dependency, preserving its module resolution anchors.
+ * @param home - the scenario's isolated Harness home.
+ * @param profile - the shipped profile under test.
+ * @param patchFile - the fixture patch whose relative module names resolve at its source.
+ * @param stage - whether this external bundle is required or optional at startup.
+ */
+export function stageStartupBundle(
+  home: string,
+  profile: 'web' | 'sdk' | 'headless',
+  patchFile: string,
+  stage: ProfileLayer['stage'] = 'runtime',
+): void {
+  const dir = join(home, 'profiles', profile)
+  const template = PROFILE_TEMPLATES[profile]
+  if (template === undefined) throw new Error(`Missing shipped profile template: ${profile}`)
+  if (!existsSync(join(dir, 'package.json'))) initProfile(dir, template.bundles, template.patchReload)
+  const name = '@fixture/startup-policy'
+  const bundle = join(dir, 'node_modules', name)
+  mkdirSync(bundle, { recursive: true })
+  writeFileSync(join(bundle, 'package.json'), JSON.stringify({
+    name, version: '1.0.0', private: true, type: 'module', dsh: { bundle: { patch: './cordis.patch.json', stage } },
+  }))
+  writeFileSync(join(bundle, 'cordis.patch.json'), JSON.stringify(loadOverlayPatches('startup fixture', patchFile)))
+  const manifest = readProfileManifest('startup fixture', dir)
+  writeProfileManifest(dir, {
+    ...manifest,
+    dependencies: { ...manifest.dependencies, [name]: '1.0.0' },
+    dsh: { ...manifest.dsh, profile: { ...manifest.dsh?.profile, bundles: [...manifest.dsh?.profile?.bundles ?? [], name] } },
+  })
+}