docs: trim CoT leakage from post-purge prose

Remove dead design-session citations, change narration, indexical
stamps, and review-adjacent justification found by the
dsh-trim-cot-leakage recall batteries in prose that landed after the
last purge. Bilingual README pairs are re-recorded.
This commit is contained in:
Tianyi Cui
2026-08-22 20:35:11 +08:00
parent efdafb8610
commit 17f85bdbcd
21 changed files with 88 additions and 118 deletions
+2 -2
View File
@@ -92,8 +92,8 @@ describe('web e2e: agent-preset authoring is a host-side copy', () => {
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(SECTION_EXPECTED, snapshot, MODE)
// The intro carries the guidance a create button used to imply, and the
// shipped rows offer view/copy but never delete or a location — their
// The intro states the copy path directly, and the shipped rows offer
// view/copy but never delete or a location — their
// install is overwritten by upgrades and is not the user's to manage.
expect(snapshot).toContain('或用「创造模式」让 Agent 帮你创建')
expect(snapshot).not.toContain('新建预设')
@@ -52,8 +52,8 @@ describe.skipIf(MODE === 'record')('web e2e: another usable provider ends first-
await page.getByRole('button', { name: '设置', exact: true }).click()
const settings = page.getByRole('dialog', { name: '设置' })
await settings.waitFor({ timeout: 10_000 })
// The onboarding step no longer navigates into Settings on dismissal, so
// enter the Models section explicitly before exercising its normal cards.
// Dismissing the onboarding step leaves Settings closed, so enter the
// Models section explicitly before exercising its normal cards.
await settings.getByRole('button', { name: '模型' }).click()
const setupKey = settings.getByRole('textbox', { name: 'API 密钥', exact: true })
await setupKey.waitFor({ timeout: 10_000 })
@@ -8,8 +8,8 @@
* published tarball has the same integrity is skipped, and a version whose
* published tarball differs fails the run — that last case means the content
* changed without a version bump. Skipping on identical integrity is what makes
* re-running the publish step over the same artifact safe, which matters here
* because a partial publication used to leave no way forward: republishing an
* re-running the publish step over the same artifact safe. Without the
* integrity skip, a partial publication has no way forward: republishing an
* existing version fails permanently.
*
* Usage: `node scripts/publish-release.mjs [packed dir]`.
@@ -808,8 +808,8 @@ describe('hand-declared providers', () => {
})
it('names the provider as the refreshed directory reports it after a rename', async () => {
// The status line used to echo the target captured when the card opened,
// which never lied while the name could not change. It can now.
// A name can change after the card opens, so the saved status reads the
// refreshed directory name rather than the target captured at open.
const { face } = await mountSection({
providers: { 'acme-gateway': { displayName: 'Acme Gateway', api: 'openai-completions' } },
declaredRoutes: ['acme-gateway'],
@@ -69,8 +69,9 @@ describe('tool-call-model', () => {
expect(model.title).toBe('Tool call')
})
it('has dropped the v2 mount verbs that no longer exist', () => {
// Keeping them would be a mapping for a tool nothing can call.
it('renders v2 mount verbs with no current tool as generic calls', () => {
// No current tool implements these v2 verbs, so a mapping would be
// unreachable.
expect(classifyTool('cordis_mount')).toBe('others')
expect(toolRowModel('cordis_mount', running({ name: 'cordis_mount', argsRaw: '{}' })).title).toBe('Tool call')
expect(toolRowModel('cordis_unmount', running({ name: 'cordis_unmount', argsRaw: '{}' })).title).toBe('Tool call')
+1 -1
View File
@@ -8,7 +8,7 @@
折叠搜索是视图和添加操作旁的一枚区头按钮。在轨道中,添加和搜索会渲染为沿外壳共用横向进入路径移动的 36px 控件。激活搜索后,输入框会扩展并占据区头;点击外部只会收起经清除首尾空白后为空的查询——但轨道搜索手势仍在进行期间(直至列滑动结束、焦点落入输入框)除外,这样触发展开的那次点击不会收起它刚打开的搜索——而清除控件总会重置并收起搜索。非空白查询会以单一扁平结果列表替代任一浏览模式:不区分大小写的标题和 Workspace 子串匹配项会立即显示,经 250 ms 防抖的 Host 请求则会加入经过排序的当前对话内容匹配项及其摘要片段。英文搜索输入框及其防御性请求路径会移除 NUL,将查询限制在传输 schema 规定的 500 个 UTF-16 代码单元内且不会拆分代理项对,并保留现有的防抖与取消行为。每次新查询都会中止前一个请求;内容搜索失败时,元数据匹配项仍会显示,同时给出警告。列表最多显示 20 条结果,并会在查询过宽时提示用户缩小范围;打开所选 Session 时既不会清除查询,也不会跳转至特定事件。
该选择器通过全局 `useWorkspaces` hook 列出真实的 Host Workspace 实体。选择 Workspace 会调用 slot owner 的 `onPick` 回调,重新定位前端 Session 对象。不同的规范化路径即使 basename 和显示标题相同,仍会作为由 id 区分的独立 Workspace;侧边栏的悬停详情把 POSIX 家目录及其后代显示为 `~``~/…`,Windows 路径保持原样。每个注册各自声明一个**目录流子 slot**(`single` kind`conversation.hero.workspace.directoryFlow``sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——标准组合使用 [`-native`](../../host/directory-picker-native/README.zh.md) 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的 **添加工作区…** 操作仅在当前界面的 slot 被占用时渲染(每次菜单渲染读取占用状态;slot 为空意味着该组合没有目录选择能力——seam 文档化的无流程默认行为,此时侧边栏区头直接不渲染添加按钮,而非留下一个点了没反应的按钮)。本包持有触发与接纳:占用方通过 slot 的属主交互约定(`open`/`busy`/`onPicked`/`onCancel`/`onError`)每次打开上报一个所选路径,owner 通过对象层接纳它,并等待 Workspace 列表投影刷新后才选中已提交的 Workspace;取消操作不会显示提示,错误落入可重试的文件夹对话框,其 **重新选择** 会重新打开流程。添加只有一条路径:占用者自带的新建文件夹能力已经覆盖了全新目录,因此不再单设按名称创建对话框。菜单只在确有多个目标可选时出现——没有 Workspace 可列时,锚点手势直接拉起流程,而不是弹出只有一行的浮层;在列表基线落地前,空列表不算最终结果。运行时 Session 与 Workspace 服务负责物化。Workspace 行内的 Delete 操作会打开确认框,说明保留边界、阻止重复提交,并在失败时保持打开;成功后,该分组会被移除,其 Session 则留在 Ungrouped 下。Session 行内的 Rename 操作打开同款浏览器持有的对话框,并以该行的显示标题预填:客户端不设名称冲突规则(host 负责规范化,可能以 `title-invalid` 拒绝,错误渲染在对话框告警区);确认未修改的标题是有意允许的——这正是把当前自动标题钉住、不被重新生成覆盖的手势。Session 行内的 Archive 操作不经确认对话框直接提交(非破坏性:日志和 workspace 记账席位保持不变),通过 `ctx.workspaces.archiveSession` 归档;归档集合回声落地后,该行从所有分组视图——workspace 分组、Ungrouped、内容搜索和平铺列表——中消失,失败只作为控制台诊断输出,树保持不变。空白的「新会话」行只是占位符:不渲染行菜单和时间标签(其中还没有发生任何事),重命名、fork 和归档都从首条提示词落地后才可用。
该选择器通过全局 `useWorkspaces` hook 列出真实的 Host Workspace 实体。选择 Workspace 会调用 slot owner 的 `onPick` 回调,重新定位前端 Session 对象。不同的规范化路径即使 basename 和显示标题相同,仍会作为由 id 区分的独立 Workspace;侧边栏的悬停详情把 POSIX 家目录及其后代显示为 `~``~/…`,Windows 路径保持原样。每个注册各自声明一个**目录流子 slot**(`single` kind`conversation.hero.workspace.directoryFlow``sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——标准组合使用 [`-native`](../../host/directory-picker-native/README.zh.md) 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的 **添加工作区…** 操作仅在当前界面的 slot 被占用时渲染(每次菜单渲染读取占用状态;slot 为空意味着该组合没有目录选择能力——seam 文档化的无流程默认行为,此时侧边栏区头直接不渲染添加按钮,而非留下一个点了没反应的按钮)。本包持有触发与接纳:占用方通过 slot 的属主交互约定(`open`/`busy`/`onPicked`/`onCancel`/`onError`)每次打开上报一个所选路径,owner 通过对象层接纳它,并等待 Workspace 列表投影刷新后才选中已提交的 Workspace;取消操作不会显示提示,错误落入可重试的文件夹对话框,其 **重新选择** 会重新打开流程。添加只有一条路径:占用者自带的新建文件夹能力已经覆盖了全新目录,因此不设独立的按名称创建对话框。菜单只在确有多个目标可选时出现——没有 Workspace 可列时,锚点手势直接拉起流程,而不是弹出只有一行的浮层;在列表基线落地前,空列表不算最终结果。运行时 Session 与 Workspace 服务负责物化。Workspace 行内的 Delete 操作会打开确认框,说明保留边界、阻止重复提交,并在失败时保持打开;成功后,该分组会被移除,其 Session 则留在 Ungrouped 下。Session 行内的 Rename 操作打开同款浏览器持有的对话框,并以该行的显示标题预填:客户端不设名称冲突规则(host 负责规范化,可能以 `title-invalid` 拒绝,错误渲染在对话框告警区);确认未修改的标题是有意允许的——这正是把当前自动标题钉住、使其不被重新生成覆盖的手势。Session 行内的 Archive 操作不经确认对话框直接提交(非破坏性:日志和 workspace 记账席位保持不变),通过 `ctx.workspaces.archiveSession` 归档;归档集合回声落地后,该行从所有分组视图——workspace 分组、Ungrouped、内容搜索和平铺列表——中消失,失败只作为控制台诊断输出,树保持不变。空白的「新会话」行只是占位符:不渲染行菜单和时间标签(其中还没有发生任何事),重命名、fork 和归档都从首条提示词落地后才可用。 (docs: trim CoT leakage from post-purge prose)
Workspace 和 Session 悬浮卡片会复制对应行被截断的值:激活 Workspace 卡片会写入其完整目录路径,激活非空白 Session 卡片则会写入其完整显示标题。临时的空白「新会话」卡片保持只读,因为其本地化标签是占位文案,并非会话内容。只有浏览器接受剪贴板写入后,卡片才会显示由字典提供的已复制状态。
@@ -4,12 +4,9 @@
* product.
*
* pnpm creates a workspace package's bin link only when the link target exists
* at install time. Pointing the bin straight at `lib/bin.js` a build product
* left the link uncreated on every clean checkout, so the command was missing
* from `node_modules/.bin` even after a build produced the file, and only an
* install that happened to follow a build brought it back. This file is
* committed, so the link is always created; the build product is resolved when
* the command actually runs.
* at install time. `lib/bin.js` is a build product and is absent on a clean
* checkout, so this committed file is the link target; it forwards to the build
* product when the command runs.
* @module @deepseek-ai/dsh-experimental-webworker-packer/bin
*/
import { existsSync } from 'node:fs'
@@ -8,8 +8,9 @@
* answers `/`, the directory that actually holds the entry. Node's three are
* purely lexical and answer `/a/b`. A `node:path` proxy owes callers Node's
* literal answers, so it needs its own port of Node's implementation rather than
* a facade over this module (`apps/web-preview` keeps one; the divergence covers
* 45 of ~200 cases, all in these three functions).
* a facade over this module; the divergence covers 45 of ~200 cases, all in
* these three functions, and `../../tests/node/path-diff.spec.ts` enumerates
* them.
* @module @deepseek-ai/dsh-experimental-webworker-runtime/src/module-system/posix-path
*/
@@ -2,7 +2,8 @@
* `node:path` for the worker: the POSIX algorithm, transliterated from Node's
* implementation. It is NOT a face over the worker host's `posixPath`: that helper
* normalizes before splitting, so `dirname('/a/b/..')` answers `/` where Node
* answers `/a/b` (45 cases diverge `.artifacts/p2/path-diff.ts` enumerates them).
* answers `/a/b` (45 cases diverge; `../../../../tests/node/path-diff.spec.ts`
* enumerates them).
* A `node:` proxy has to answer what Node answers, since VFS paths were built with
* Node semantics. `win32` members throw: the worker host reports
* `process.platform === 'linux'`, so a Windows branch means a bug.
@@ -1,6 +1,6 @@
/**
* `node:worker_threads` stub. Nested workers are out of scope for v1, so the
* workflow and code-runtime plugin bodies mount and fail on use. The
* `node:worker_threads` stub. Nested workers are unsupported, so the workflow
* and code-runtime plugin bodies mount and fail on use. The
* thread-identity values are real: they say "this is the main thread", which is
* what the worker host is from the tree's point of view.
*/
@@ -9,30 +9,10 @@
* emits, so a rolldown upgrade that starts emitting an unseen module form shows
* up here first.
*
* Consolidated from `.artifacts/w0-lexer-probe.ts` (part 2). Two deliberate
* changes for the terminal form:
*
* 1. **No `es-module-lexer`.** The lexer was retired as a runtime dependency
* when the single acorn pass replaced the two-pass pipeline, so the
* statistics it used to contribute are counted from the acorn AST instead.
* The probe's part 1 (lexer field semantics over 20 sample forms) is dropped
* entirely: it documented the behaviour of a component that no longer runs.
* The forms themselves are covered as emitted-code assertions in
* `transform-check.ts`.
* 2. **The baseline exemptions are a pinned list, not a count.** Four files
* cannot be imported by Node in this repository for reasons unrelated to the
* transform; the probe merely counted them, so a fifth would have gone
* unnoticed. Here they are named, and an unexpected member fails the run.
*
* Not consolidated: `.artifacts/v3-oracle.ts`, the byte-for-byte comparison
* against the retired lexer pipeline. It was a **retirement gate** and it has
* been through (`files=228 residualDifferences=0 lineDrift=0`). Keeping it as a
* standing check would mean keeping two abandoned implementations alive
* (`.artifacts/oracle-esm-to-cjs.ts`, `.artifacts/oracle-rewrite-await.ts`)
* forever to compare against. The one real defect it caught that no other signal
* could — `new.target` is also a `MetaProperty` — is preserved as a direct
* assertion (`transform-check.ts`, trap 8), which is where that knowledge
* belongs now.
* Module-syntax statistics are counted from the acorn AST, so the check has no
* separate lexer dependency. Baseline exemptions are a pinned list, not a count:
* four files cannot be imported by Node in this repository for reasons unrelated
* to the transform, and an unexpected member fails the run.
*
* Cost: this walks the whole build output and imports every bundle, so it takes
* tens of seconds and needs `pnpm run build:lib:host` to have run. It is a
@@ -275,7 +255,7 @@ async function runTransformed(code: string, path: string): Promise<Record<string
return exports
}
/** Module-syntax counts read from the AST, replacing what the retired lexer reported. */
/** Module-syntax counts read from the AST. */
interface Counts {
staticImports: number
dynamicImports: number
@@ -369,7 +349,7 @@ if (files.length === 0) {
// The debugging contract, over the whole corpus: a transformed body has the
// same line count as its source, so a stack frame still points at the right
// line. This is the property the retired oracle measured as "line drift".
// line.
const sourceLines = source.split('\n').length
const codeLines = code.split('\n').length
if (sourceLines !== codeLines) {
@@ -2,7 +2,7 @@
* Semantic check of the worker module transform (`src/compile/transform.ts`): what the
* emitted CommonJS body looks like for each module form, how suspension points
* are rewritten, that line numbers survive, which forms are refused, and that
* every trap the retired lexer pipeline hit stays fixed.
* every covered trap form stays fixed.
*
* Scope boundary: this file checks the transform itself; the image collector's
* loop around it is covered by the packer's `transform-image.spec.ts`.
@@ -12,9 +12,10 @@
* one — the code parses as script, publishes the right bindings, keeps line
* count, and routes suspension through `__als`.
*
* The trap cases are the module forms that broke real boots under the retired
* lexer pipeline. Five cannot recur under an AST pass, but retaining them pins
* the behavior against a future parser swap.
* The trap cases are module forms that break a boot when the transform
* mishandles them. Five traps cannot recur while the AST pass is the parser,
* but they stay checked because a future parser swap would reintroduce exactly
* them.
*/
import { expect, test } from 'vitest'
import { parse } from 'acorn'
@@ -208,7 +209,7 @@ check(
{
// Local exports are getters, so a later assignment is observable through
// `exports` — the ESM live-binding property the report calls out explicitly.
// `exports` — the ESM live-binding property.
const code = transformModule('export let counter = 0\nexport function bump() { counter += 1 }\n', 'probe.js')
parsesAsScript('live binding', code)
const exports = runBody(code)
@@ -224,9 +225,7 @@ check(
}
{
// Trap 4 in the lexer report: `export const a = 1, b = 2` reported only the
// first declarator, so the AST pass upgraded this from "loud refusal" to
// "correctly supported". Both bindings must appear.
// Trap 4: a multi-declarator export publishes every binding.
const code = transformModule('export const a = 1, b = 2\n', 'probe.js')
parsesAsScript('multi-declarator export', code)
const exports = runBody(code)
@@ -415,7 +414,7 @@ function recordingAls(): { als: Record<string, unknown>; calls: string[] } {
{
// for-await desugars to an explicit loop; `return()` must run only on abrupt
// completion, which is the language rule the report calls out. The two
// completion, which is the language rule. The two
// completion paths need two different loop bodies, so they are separate cases.
const plain = 'export const run = async (src) => { const seen = []\n'
+ 'for await (const item of src) { seen.push(item) }\n'
@@ -594,24 +593,24 @@ refuses('unparseable source is refused', 'export const = \n', 'parse failed')
}
// ---------------------------------------------------------------------------
// 9. Trap regressions. Each case broke a real boot under the retired lexer
// pipeline; the AST pass must keep them fixed.
// 9. Trap regressions. Each case is a module form that breaks a boot when the
// transform mishandles it; the AST pass must keep them fixed.
// ---------------------------------------------------------------------------
{
// Trap 1: a file with no module syntax can still contain a dynamic import.
// Early-returning on "no module syntax" left it unrewritten and it escaped to
// the host engine's parser.
// Trap 1: a file with no module syntax can still contain a dynamic import. A
// transform that skips such files would leave it unrewritten, and it would
// escape to the host engine's parser.
const code = transformModule("module.exports = () => import('./x.js')\n", 'probe.js')
contains('trap 1: dynamic import in a CommonJS file is still rewritten', code, '__dsh$dynImport')
parsesAsScript('trap 1', code)
}
{
// Trap 2: `export {}` is a bundler module marker. The lexer reported nothing
// for it, so it survived into `new Function` as `Unexpected token 'export'`.
// The needle is the keyword in statement position, since `exports.` in the
// prologue legitimately contains the same letters.
// Trap 2: `export {}` is a bundler module marker and must be removed before
// `new Function` parses the body. The needle is the keyword in statement
// position, since `exports.` in the prologue legitimately contains the same
// letters.
const code = transformModule('export {};\n', 'probe.js')
lacks('trap 2: bare export {} is removed', code, 'export {')
lacks('trap 2: no export keyword survives', code, 'export;')
@@ -630,10 +629,10 @@ refuses('unparseable source is refused', 'export const = \n', 'parse failed')
}
{
// Trap 6, the most costly one: a block comment before a class member named
// `import` made the lexer report a dynamic import, renaming
// `EntryTree.prototype.import` and breaking the loading chain at
// `Entry._init` with "this.parent.tree.import is not a function".
// Trap 6: a block comment before a class member named `import` must not be
// treated as a dynamic import. Renaming `EntryTree.prototype.import` breaks
// the loading chain at `Entry._init` with
// "this.parent.tree.import is not a function".
const source = 'export class A {\n /** doc */ import(name) { return name }\n}\n'
const code = transformModule(source, 'probe.js')
lacks('trap 6: a method named import is not rewritten', code, '__dsh$dynImport')
@@ -643,19 +642,19 @@ refuses('unparseable source is refused', 'export const = \n', 'parse failed')
}
{
// Trap 7: a comment between `export` and the declaration keyword made the
// gap-matching regex miss, refusing zod's `export /*@__NO_SIDE_EFFECTS__*/ function`
// and taking 30-odd roster rows down with it.
// Trap 7: a comment between `export` and the declaration keyword must not
// hide the declaration; refusing zod's
// `export /*@__NO_SIDE_EFFECTS__*/ function` takes 30-odd roster rows down
// with it.
const code = transformModule('export /*@__NO_SIDE_EFFECTS__*/ function $constructor(x) { return x }\n', 'probe.js')
parsesAsScript('trap 7', code)
check('trap 7: export with an interposed comment still publishes', typeof runBody(code).$constructor, 'function')
}
{
// The trap the AST pass introduced and the byte-level oracle caught:
// `new.target` is also a MetaProperty. Replacing every MetaProperty made
// `new.target === Cls` permanently false, silently disabling abstract-seam
// guards in `jobs` and `llm`.
// `new.target` is also a MetaProperty. Replacing every MetaProperty would
// make `new.target === Cls` permanently false, silently disabling
// abstract-seam guards in `jobs` and `llm`.
const source = 'export class Base {\n constructor() { this.direct = new.target === Base }\n}\n'
const code = transformModule(source, 'probe.js')
contains('trap 8: new.target survives verbatim', code, 'new.target')
@@ -668,9 +667,9 @@ refuses('unparseable source is refused', 'export const = \n', 'parse failed')
}
{
// Shebang handling (found while packing `yaml/bin.mjs`): `#!` is only legal at
// offset 0, which the prologue occupies. It is commented out in place so both
// offsets and the line count stay put.
// Shebang handling: `#!` is only legal at offset 0, which the prologue
// occupies. It is commented out in place so both offsets and the line count
// stay put.
const source = '#!/usr/bin/env node\nexport const main = 1\n'
const code = transformModule(source, 'probe.js')
lacks('shebang is not left in the emitted body', code, '#!')
@@ -4,10 +4,9 @@
* Cordis's `LoggerService` accepts every message and, with no exporter mounted,
* only fills a ring buffer. No profile in this repository mounts one, so a
* provider that fails and is skipped — the skill registry logs exactly that —
* used to look identical to one that found nothing. That is how an empty skill
* catalog hid a filesystem fault through two rounds of diagnosis, so the sink is
* exercised here rather than trusted: a diagnostic nothing runs is a diagnostic
* that silently stops working.
* is indistinguishable from one that found nothing. The sink is exercised here
* rather than trusted: a diagnostic that runs nothing is a diagnostic that
* silently stops working.
*/
import { afterEach, describe, expect, it, vi } from 'vitest'
import { installLogSink, type LogExporter, type LogMessage } from '../src/worker-host.ts'
@@ -1,13 +1,12 @@
/**
* The `node:child_process` face over the in-worker shell, and the ladder above
* it: the REAL local subprocess service, running unmodified against this
* module instead of a host kernel. That ladder is what the bash tool walks in
* the browser, so proving it here is what makes the browser probe a
* confirmation rather than the only evidence.
* module instead of a host kernel. The bash tool walks this same ladder in the
* browser.
*
* A Node test host has no DOM `Worker`, so the commands here run through the
* inline strategy; the worker strategy and its frames are proven in
* `../shell/shell-process.spec.ts`, and both meet again in the preview probe.
* `../shell/shell-process.spec.ts`.
*
* `process.kill` is redirected to the worker's process table for the same
* reason the worker does it: the subprocess service polls process-group
@@ -6,12 +6,11 @@
* `node:path/posix`", so Node itself is the oracle and every case is compared
* rather than asserted against a hand-written expectation. The corpus is the
* shapes a VFS path actually takes (absolute image paths, `node_modules`
* specifiers, `.bin` entries) plus the edge forms that historically diverge
* (repeated slashes, trailing dots, `..` past the root).
* specifiers, `.bin` entries) plus the edge forms that diverge between the two
* implementations (repeated slashes, trailing dots, `..` past the root).
*
* Migrated from apps/web-preview/scripts/checks/path-diff.ts. Imports go through
* the package name so the harness and the shim resolve to one module instance
* (see `../polyfill/als-shim.spec.ts` for why that matters).
* Imports go through the package name so the harness and the shim resolve to one
* module instance (see `../polyfill/als-shim.spec.ts` for why that matters).
*/
import { expect, test } from 'vitest'
import { posix as nodePosix } from 'node:path'
@@ -16,8 +16,7 @@
* - both completion paths do this, which is why the token always fulfills.
*
* The shim-backed end of the same contract (does a real AsyncLocalStorage
* actually fold, do the hooks cover timers) is `als-shim.spec.ts`, and the
* cross-session behavioural proof is the browser concurrency probe. This file is
* actually fold, do the hooks cover timers) is `als-shim.spec.ts`. This file is
* the middle layer: the protocol, in isolation.
*/
import { expect, test } from 'vitest'
@@ -121,8 +120,8 @@ function recordingCausality(): {
}
{
// The rejection path restores too, and only then rethrows: a catch clause must
// observe the caller's store, which is the case the browser probe pinned.
// The rejection path restores too, and only then rethrows: a catch clause
// must observe the caller's store.
const state = recordingCausality()
const als = createAlsRuntime(state.causality)
state.current = 'session-C'
@@ -310,9 +309,9 @@ function recordingCausality(): {
}
// ---------------------------------------------------------------------------
// 6. The inert runtime. `?als=inert` is the browser probe's control arm: the
// rewrite still runs and still hops a microtask, but no state moves. That
// control must be genuinely inert, or the probe loses its discriminating power.
// 6. The inert runtime. Without a causality face, the rewrite still runs and
// still hops a microtask, but no state moves. A comparison arm built on this
// mode must be genuinely inert, or the comparison proves nothing.
// ---------------------------------------------------------------------------
{
@@ -17,15 +17,11 @@
*
* Scope boundary: this file owns the shim (the state). `als-runtime.spec.ts`
* owns the protocol that moves snapshots around, with the causality face stubbed.
* The cross-session end-to-end proof is the browser concurrency probe, whose
* control arm (`?als=inert`) relies on the protocol being genuinely inert.
*
* Migrated from apps/web-preview/scripts/checks/als-check.ts after the Node
* compatibility layer was reorganized into implemented/mock/globals. Every import
* goes through the **package name**, not a relative path: a check that reached
* built `lib/` while the shim resolved by package name to `src/` produced two
* module instances and a shim mounted in the wrong world (the `fs-check`
* incident "no filesystem is mounted"). One resolution path per module.
* Every import goes through the **package name**, not a relative path: a check
* that reaches built `lib/` while the shim resolves by package name to `src/`
* gets two module instances and a shim mounted in the wrong world (the failure
* mode asserted in `../node/fs.spec.ts`). One resolution path per module.
*/
import { expect, test } from 'vitest'
import {
@@ -6,8 +6,7 @@
* (`runShellProcess`) against the REAL host half, so the frames, the
* filesystem service, and the termination ladder are the shipped ones only
* the thread boundary is simulated, because a Node test host has no DOM
* `Worker` to cross. That a browser worker really can start a nested worker
* and terminate it mid-burn is measured separately, in the preview probe.
* `Worker` to cross. The real browser Worker boundary is not exercised here.
*/
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import { MemoryVfs } from '@deepseek-ai/dsh-experimental-webworker-runtime/src/storage/memory.ts'
+2 -2
View File
@@ -1032,8 +1032,8 @@ describe('compat switches', () => {
})
it('refuses a compat key no wire protocol declares instead of dropping it', () => {
// The silent drop is what let an unreadable switch look applied: schemastery
// passes unknown keys through, and resolution used to read only two fields.
// Schemastery passes unknown keys through, so silently dropping one would
// make an unreadable switch look applied; the resolver must refuse it.
expect(() => resolveProfiles({
'acme-gateway': {
api: 'openai-completions',
@@ -331,9 +331,9 @@ describe.skipIf(!isWin32 || !pwshAvailable())('windows-acl runner', () => {
// workspace-write keeps the ACE standing for the server lifetime. After
// switching to read-only, the restricted token's read-only list must carry NO
// capability SID — the standing ACE stays but the pass-2 check cannot use
// it, so the workspace write is denied (previously it LEAKED). The
// switch back reuses the SAME standing ACE: the re-upgrade write lands
// without any re-grant.
// it, so the workspace write is denied instead of leaking through the
// standing ACE. The switch back reuses the SAME standing ACE: the
// re-upgrade write lands without any re-grant.
const writeSid = workspaceWriteSid(writableDir)
const privateTemp = join(isolatedTemp, 'mode-switch-temp')
mkdirSync(privateTemp)
@@ -152,9 +152,9 @@ describe('SessionProjectionRegistry drive', () => {
first()
// The regression this counts against: one session ending used to strip
// the projection from every other live session, because the first
// registrant owned the only disposer.
// The regression this counts against: without last-release semantics, one
// session ending strips the projection from every other live session,
// because the first registrant owns the only disposer.
expect(ctx.sessionProjections.snapshot(session).values['test/marks']).toEqual({ marks: ['kept'] })
second()
expect(ctx.sessionProjections.snapshot(session).values).toEqual({})