Files
dsh_ai1net_server/交付物/验证脚本/v-neg.sh
T
admin c1b5e4d966 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/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

160 lines
6.9 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# =============================================================================
# v-neg.sh —— S0 尺子④:负控驱动(**S0 的唯一完成判据**)
# 规格来源:交付物/功能打包到基础插件-分步实施方案-20260926.md §3.10.1(负控驱动行)· §3.10.2 S0-5
# -----------------------------------------------------------------------------
# 断言(两个都成立才 rc=0 绿)
# ① 改坏靶子一处 ⇒ v-plugin.sh 对应面 **必须红(rc≠0)**
# ② 改回(用未改动的靶子)⇒ 同一条判据 **必须绿(rc=0)**
# -----------------------------------------------------------------------------
# 🔴 铁律:改坏**只动 tmp/ 里的副本** ——
# ⛔ 不碰任何其他工作区的文件(用户 2026-09-24 明令);⛔ 不改真 profile、⛔ 不改真日志
# 连跑两次一致的比较对象:见 v-baseline.sh --stable(本脚本只负责红/绿双态)
# 用法
# bash v-neg.sh 装配|取数|回调|运行|all
# =============================================================================
set -u
SELF_DIR="$(cd "$(dirname "$0")" && pwd -W)"
[ -d "$SELF_DIR" ] || SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
WS="$(cd "$SELF_DIR/../.." && pwd -W)"
[ -d "$WS" ] || WS="$(cd "$SELF_DIR/../.." && pwd)"
OUT="${P39_OUT:-$WS/tmp/p39}"
NEG="$OUT/neg"
DATE="$(date +%Y%m%d)"
REC="$OUT/neg-记录-$DATE.txt"
VPLUGIN="$SELF_DIR/v-plugin.sh"
DSH_DESKTOP_HOME="${DSH_DESKTOP_HOME:-E:/github/dsh-desktop-0.1.7rc2/apps/desktop/.desktop-build/development/home-adapt}"
TARGET="@dsh-client/portal-first-screen"
PROFILE_JSON="$DSH_DESKTOP_HOME/profiles/desktop/package.json"
mkdir -p "$NEG"
: > "$REC"
CASE="${1:-}"
[ -z "$CASE" ] && { echo "用法: v-neg.sh <装配|取数|回调|运行|all>"; exit 2; }
log() { printf '%s\n' "$*" >> "$REC"; }
# 跑一条判据,返回 rc;输出同时落记录
run_face() { # $1=face $2..=额外参数
local face="$1"; shift
local tmpf="$OUT/.neg-last.out"
bash "$VPLUGIN" "$face" "$@" > "$tmpf" 2>&1
local rc=$?
cat "$tmpf" >> "$REC"
printf '%s' "$rc"
}
# 找靶子入口(真件)
resolve_entry() {
"$NODE_BIN" -e 'const{createRequire}=require("module");const r=createRequire(process.argv[1]);process.stdout.write(r.resolve(process.argv[2]))' "$PROFILE_JSON" "$TARGET" 2>/dev/null
}
NODE_BIN="${NODE:-E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3/node.exe}"
REAL_ENTRY="$(resolve_entry)"
REAL_PKG_JSON="$("$NODE_BIN" -e 'const{createRequire}=require("module");const r=createRequire(process.argv[1]);process.stdout.write(r.resolve(process.argv[2]+"/package.json"))' "$PROFILE_JSON" "$TARGET" 2>/dev/null)"
REAL_PKG_DIR="$(dirname "$REAL_PKG_JSON")"
overall=0
declare -a SUMMARY
one_case() { # $1 = case name
local c="$1"
local dir="$NEG/$c"; rm -rf "$dir"; mkdir -p "$dir"
log ""
log "════ 负控 case【$c】 $(date -Iseconds) ════"
local HEALTHY_RC BROKEN_RC=1
case "$c" in
装配)
# 破坏点(单变量):从 profile 的 dsh.profile.bundles 里删掉靶子那一行
cp "$PROFILE_JSON" "$dir/package.json"
"$PY_BIN" - "$dir/package.json" "$TARGET" <<'PYEOF'
import json, sys
p, t = sys.argv[1], sys.argv[2]
d = json.load(open(p, encoding='utf-8'))
b = d['dsh']['profile']['bundles']
d['dsh']['profile']['bundles'] = [x for x in b if x != t]
json.dump(d, open(p, 'w', encoding='utf-8'), indent=2, ensure_ascii=False)
PYEOF
# 给副本补上**靶子自身**的最小 node_modules 投影(只复制靶子,不投影整棵树)
# ⇒ 破坏点收窄为单变量:只有 profile 的 bundles 声明缺了靶子
STUB="$dir/node_modules/@dsh-client/portal-first-screen"
mkdir -p "$STUB"
cp "$REAL_PKG_JSON" "$STUB/package.json"
cp -r "$REAL_PKG_DIR/lib" "$STUB/lib"
[ -f "$REAL_PKG_DIR/cordis.patch.yml" ] && cp "$REAL_PKG_DIR/cordis.patch.yml" "$STUB/cordis.patch.yml"
JUNC="stub"
log "改坏方式:删掉副本 profile 的 bundles 行;node_modules 投影=已建(stub 只含靶子自身 ⇒ 单变量:只有 bundles 声明这一处被改坏)"
BROKEN_RC="$(run_face 装配 --profile-json "$dir/package.json")"
log ""
log "▶ 改坏后:rc=$BROKEN_RC(期望 ≠0)"
;;
取数)
cp "$REAL_ENTRY" "$dir/index.js"
sed -i 's/^export const name = /export const nameQ39broken = /' "$dir/index.js"
log "改坏方式:副本产物里把导出 name 改名(单变量)"
BROKEN_RC="$(run_face 取数 --entry "$dir/index.js")"
log ""
log "▶ 改坏后:rc=$BROKEN_RC(期望 ≠0)"
;;
回调)
cp "$REAL_ENTRY" "$dir/index.js"
sed -i 's/^export function apply(/export function applyQ39broken(/' "$dir/index.js"
log "改坏方式:副本产物里把 apply 改名(单变量)"
BROKEN_RC="$(run_face 回调 --entry "$dir/index.js")"
log ""
log "▶ 改坏后:rc=$BROKEN_RC(期望 ≠0)"
;;
运行)
REAL_LOG="$(ls -1t "$DSH_DESKTOP_HOME/logs/"startup-*.log 2>/dev/null | head -1)"
if [ -z "$REAL_LOG" ]; then
log "⚠️ 无真启动日志 ⇒ 本 case 未取到(rc=3)"
SUMMARY+=("$c: 未取到(无真启动日志)")
overall=1
return
fi
cp "$REAL_LOG" "$dir/fake-failed.log"
printf "\n Failed plugins (1):\n %s\n Error: [S0 负控构造] 靶子启动失败(⛔ 构造副本,真日志未被改动)\n" "$TARGET" >> "$dir/fake-failed.log"
log "改坏方式:把真日志**副本**改出一行靶子失败条目(单变量;⛔ 真日志未动)"
BROKEN_RC="$(run_face 运行 --log "$dir/fake-failed.log")"
log ""
log "▶ 改坏后:rc=$BROKEN_RC(期望 ≠0)"
;;
esac
HEALTHY_RC="$(run_face "$c")"
log ""
log "▶ 改回(真靶子):rc=$HEALTHY_RC(期望 0)"
local ok=1
[ "$BROKEN_RC" = "0" ] && ok=0
[ "$HEALTHY_RC" != "0" ] && ok=0
if [ "$ok" = "1" ]; then
log "✅ case【$c】负控通过:改坏 rc=$BROKEN_RC(红)/改回 rc=$HEALTHY_RC(绿)"
SUMMARY+=("$c: ✅ 通过(改坏 rc=$BROKEN_RC 红 · 改回 rc=$HEALTHY_RC 绿)")
else
log "❌ case【$c】负控未通过:改坏 rc=$BROKEN_RC / 改回 rc=$HEALTHY_RC(要求 改坏≠0 且 改回=0)"
SUMMARY+=("$c: ❌ 未通过(改坏 rc=$BROKEN_RC · 改回 rc=$HEALTHY_RC)")
overall=1
fi
}
PY_BIN="${PY:-E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe}"
[ -z "$REAL_ENTRY" ] || [ -f "$REAL_ENTRY" ] || { echo "⛔ 解析不到真靶子入口($TARGET)⇒ 负控前置不成立"; exit 1; }
if [ "$CASE" = "all" ]; then
for c in 装配 运行 取数 回调; do one_case "$c"; done
else
case "$CASE" in 装配|运行|取数|回调) one_case "$CASE" ;; *) echo "⛔ 未知 case:$CASE"; exit 2 ;; esac
fi
echo "════ v-neg 结果 ════"
for s in "${SUMMARY[@]}"; do echo " $s"; done
echo "记录(含全部原始输出):$REC"
[ "$overall" != "0" ] && { echo "⇒ 红:负控未全过(⛔ 不通过负控的判据不算判据)"; exit 1; }
echo "⇒ 绿:红/绿双态均成立"
exit 0