fix: address ds-review-bot round — insert-aliasing clones, settlement gates, closure module fallback
- Clone patch lists per generation (boot + composeLive): the include pushes insert rows by reference and mutates them in place, so a reused object baked user overrides into bundle rows and removal could not revert; the built-bin hot-reload e2e now asserts an override AND its removal reverting. - The headless runner awaits Loader settlement before prompting (its inject gate covers only apiProxy/httpServer) and abandons cleanly when the tree died during the wait. - healProfilesModuleFallback walks the app's full dependency+peer closure: out-of-tree plugins import seam packages (dsh-compact, dsh-subprocess, ...) that only implementations reach, and peers are how seams are declared. - Profile init writes pnpm-workspace.yaml (nodeLinker: hoisted), not .npmrc — pnpm >=10 reads settings from the workspace manifest. - Web dumps reject boot-only flags instead of printing a tree that differs from the same invocation's boot; --port validates at the flag; --dump-default-config no longer parses the (possibly broken) user layer; trustedHosts flag derivation merges over the composed value instead of replacing it; web-runtime gains surfaceContext (headless disables the GUI prompt/bash-vars the old -p never mounted); 'node_modules' is a reserved profile name; plugin-warning names the recovery step; client AGENTS.md registration surfaces point at the web-app bundle. - Ship session-reference/tmux-context/tool-ask-user as app dependencies for terminal front-door patch layers (turtle-ui), same stance as mcp-client.
This commit is contained in:
@@ -174,6 +174,36 @@ describe('headless runner', () => {
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('waits for Loader settlement and abandons the run when the tree died during it', async () => {
|
||||
const ctx = new Context()
|
||||
let err = ''
|
||||
let exited = false
|
||||
ctx.provide('headlessIo', {
|
||||
stdout: { write: () => true },
|
||||
stderr: { write: (chunk: string) => { err += chunk; return true } },
|
||||
exit: () => { exited = true },
|
||||
} satisfies HeadlessIo)
|
||||
ctx.provide('apiProxy', scriptedApi([]) as never)
|
||||
// The webserver is provided by a child fiber whose disposal (early
|
||||
// SIGTERM during the boot window) removes the service; settlement
|
||||
// resolves only afterwards, and the runner must abandon rather than
|
||||
// crash on the torn-down port read.
|
||||
const webserverFiber = ctx.plugin((childCtx: Context) => {
|
||||
childCtx.provide('httpServer', { port: 1 } as never)
|
||||
})
|
||||
await webserverFiber
|
||||
let release: () => void
|
||||
const settlement = new Promise<void>((resolve) => { release = resolve })
|
||||
ctx.provide('loader', { await: () => settlement } as never)
|
||||
apply(ctx, { task: 't' })
|
||||
await webserverFiber.dispose()
|
||||
release!()
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
expect(err).toBe('')
|
||||
expect(exited).toBe(false)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('fails loud without the launcher-owned headlessIo seam', () => {
|
||||
const ctx = new Context()
|
||||
ctx.provide('apiProxy', scriptedApi([]) as never)
|
||||
|
||||
Reference in New Issue
Block a user