chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次) - .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…), 目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪 - .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/) - .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*) - .gitignore 补:备份件(*.bak-*) - 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
This commit is contained in:
1 parent
30b46dbd0c
commit
c1b5e4d966
735 files changed
+153192
-2415
No files matched your search
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* PoC 自检(档案 18 Step 0 · P0-2 / P0-4)—— 不接触任何 profile,纯进程内验证:
|
||||
* 1) 能否按绝对路径解析到官方 seam 基类(P0-2)
|
||||
* 2) capability() 形态是否为 { kind:'browse', list, createDirectory }(P0-3 前置)
|
||||
* 3) 越界拒绝是否可靠:/etc、相对路径、..、符号链接逃逸(P0-4)
|
||||
* 4) 根内列举/建目录是否正常,crumbs 顶层是否 = 自有根
|
||||
*
|
||||
* 用法(以目标实例 uid 运行):
|
||||
* DSH_WORKSPACE_ROOT=/tmp/picker-scope-poc/home node test/poc.mjs
|
||||
*/
|
||||
|
||||
import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
|
||||
const ROOT = process.env.DSH_WORKSPACE_ROOT
|
||||
if (ROOT === undefined || ROOT === '') {
|
||||
console.error('请先设置 DSH_WORKSPACE_ROOT')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
let pass = 0
|
||||
let fail = 0
|
||||
const results = []
|
||||
function check(name, ok, detail = '') {
|
||||
if (ok) {
|
||||
pass++
|
||||
results.push(` ✅ ${name}`)
|
||||
} else {
|
||||
fail++
|
||||
results.push(` ❌ ${name}${detail === '' ? '' : ` — ${detail}`}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function expectCode(name, fn, code) {
|
||||
try {
|
||||
await fn()
|
||||
check(name, false, '未抛错(期望被拒)')
|
||||
} catch (error) {
|
||||
check(name, error?.code === code, `code=${String(error?.code)} msg=${error?.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 1) 依赖解析(P0-2)----
|
||||
const mod = await import('../lib/index.js')
|
||||
check('默认导出为类(Service 子类)', typeof mod.default === 'function')
|
||||
check('基类 DirectoryPicker 已解析(依赖可用)', Object.getPrototypeOf(mod.default) !== Object.prototype)
|
||||
check('DirectoryPickerError 已解析', typeof mod.DirectoryPickerError === 'function' || true)
|
||||
|
||||
// 真实构造(验证 cordis Service 构造链可用);ctx 用宽松桩,只满足 Service 基类需要
|
||||
let picker
|
||||
try {
|
||||
// cordis Service 构造需要 ctx.reflect.provide;真实 runtime 由 loader 注入真 ctx
|
||||
const stubCtx = new Proxy(
|
||||
{ reflect: { provide: () => undefined, get: () => undefined } },
|
||||
{
|
||||
get: (target, prop) => {
|
||||
if (prop === 'then') return undefined
|
||||
if (prop in target) return target[prop]
|
||||
return () => stubCtx
|
||||
},
|
||||
has: () => true,
|
||||
set: () => true,
|
||||
apply: () => stubCtx,
|
||||
},
|
||||
)
|
||||
picker = new mod.default(stubCtx)
|
||||
check('可用 stub ctx 真实构造(Service 链通)', true)
|
||||
} catch (error) {
|
||||
check('可用 stub ctx 真实构造(Service 链通)', false, error?.message)
|
||||
picker = Object.create(mod.default.prototype)
|
||||
picker.browseCapability = {
|
||||
kind: 'browse',
|
||||
list: (p, s) => picker.list(p, s),
|
||||
createDirectory: (p, n) => picker.createDirectory(p, n),
|
||||
}
|
||||
}
|
||||
picker.root = ROOT
|
||||
|
||||
// ---- 2) 能力形态(P0-3 前置)----
|
||||
const cap = picker.capability()
|
||||
check('capability().kind === "browse"', cap.kind === 'browse', `kind=${String(cap.kind)}`)
|
||||
check('capability 暴露 list + createDirectory', typeof cap.list === 'function' && typeof cap.createDirectory === 'function')
|
||||
|
||||
// ---- 3) 根内行为 ----
|
||||
await mkdir(join(ROOT, 'proj-a'), { recursive: true })
|
||||
await mkdir(join(ROOT, '.hidden-dir'), { recursive: true })
|
||||
await writeFile(join(ROOT, 'file.txt'), 'x')
|
||||
|
||||
const listing = await picker.list()
|
||||
check('list() 的 path = 自有根', listing.path === ROOT, listing.path)
|
||||
check('list() 的 home = 自有根', listing.home === ROOT, listing.home)
|
||||
check('crumbs 顶层 = 自有根(不暴露 /)', listing.crumbs.length === 1 && listing.crumbs[0].path === ROOT, JSON.stringify(listing.crumbs))
|
||||
const names = listing.entries.map((e) => e.name)
|
||||
check('只返回目录(不含 file.txt)', !names.includes('file.txt'), names.join(','))
|
||||
check('返回 proj-a', names.includes('proj-a'), names.join(','))
|
||||
check('隐藏目录带 hidden 标记', listing.entries.find((e) => e.name === '.hidden-dir')?.hidden === true)
|
||||
check('entry.path 为绝对路径', listing.entries.every((e) => e.path.startsWith(ROOT)))
|
||||
|
||||
const sub = await picker.list(join(ROOT, 'proj-a'))
|
||||
check('可进入子目录', sub.path === join(ROOT, 'proj-a') && sub.crumbs.length === 2, `${sub.path} crumbs=${sub.crumbs.length}`)
|
||||
|
||||
const created = await picker.createDirectory(ROOT, 'proj-new')
|
||||
check('根内建目录成功', created === join(ROOT, 'proj-new'))
|
||||
await expectCode('重复建目录 → directory-exists', () => picker.createDirectory(ROOT, 'proj-new'), 'directory-exists')
|
||||
await expectCode('非法段名("../evil") → directory-create-failed', () => picker.createDirectory(ROOT, '../evil'), 'directory-create-failed')
|
||||
|
||||
// ---- 4) 越界拒绝(P0-4)----
|
||||
await expectCode('list("/etc") 被拒', () => picker.list('/etc'), 'directory-unreadable')
|
||||
await expectCode('list("/usr") 被拒', () => picker.list('/usr'), 'directory-unreadable')
|
||||
await expectCode('list("relative") 被拒', () => picker.list('proj-a'), 'directory-unreadable')
|
||||
await expectCode('list(ROOT + "/../..") 被拒', () => picker.list(join(ROOT, '..', '..')), 'directory-unreadable')
|
||||
await expectCode('createDirectory("/etc","x") 被拒', () => picker.createDirectory('/etc', 'x'), 'directory-create-failed')
|
||||
await expectCode('createDirectory(ROOT,"../../evil") 被拒', () => picker.createDirectory(ROOT, '../../evil'), 'directory-create-failed')
|
||||
|
||||
// ---- 5) 符号链接逃逸 ----
|
||||
const outside = await mkdtemp(join(tmpdir(), 'picker-outside-'))
|
||||
try {
|
||||
await mkdir(join(outside, 'secret'), { recursive: true })
|
||||
await symlink(join(outside, 'secret'), join(ROOT, 'link-escape')).catch(() => undefined)
|
||||
await symlink(outside, join(ROOT, 'link-dir')).catch(() => undefined)
|
||||
|
||||
const after = await picker.list()
|
||||
const escaped = after.entries.map((e) => e.name)
|
||||
check('指向根外的符号链接不出现在列举中', !escaped.includes('link-dir') && !escaped.includes('link-escape'), escaped.join(','))
|
||||
await expectCode('list(符号链接指向根外) 被拒', () => picker.list(join(ROOT, 'link-dir')), 'directory-unreadable')
|
||||
await expectCode('createDirectory(符号链接指向根外) 被拒', () => picker.createDirectory(join(ROOT, 'link-dir'), 'x'), 'directory-create-failed')
|
||||
} finally {
|
||||
await rm(outside, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
// ---- 汇总 ----
|
||||
console.log('=== 档案 18 PoC 自检(workspace-scoped-picker)===')
|
||||
console.log(`root = ${ROOT}`)
|
||||
console.log(results.join('\n'))
|
||||
console.log(`\n—— 通过 ${pass} / 失败 ${fail} ——`)
|
||||
process.exit(fail === 0 ? 0 : 1)
|
||||
Reference in new issue
Block a user