16 Commits
Author SHA1 Message Date
StarAsh042 8bde913426 feat!: 删除路径防护逃生开关,系统目录改读注册表(v5.3.0) (#20)
* feat!: 删除路径防护逃生开关,系统目录改读注册表

Registry 安全扫描对 os.getenv 有确定性规则($env_read3),命中即 Flagged,
无严重度阈值。5.1.3 与 5.1.6 两轮「消除误报」只是把 os.environ.get( 换成
os.getenv(,从一个规则换到另一个规则,instances 反而 1→5。本版删除读取本身。

破坏性变更:
- 删除 ROLLINGARTIST_ALLOW_ANY_PATH 环境变量(曾完全关闭路径防护)
- 删除 ra_core.paths.is_path_guard_disabled() 及其导出

变更:
- Windows 系统目录(SystemRoot / Program Files / Program Files (x86) /
  ProgramData)改读 HKLM 注册表。注册表是权威来源(环境变量是登录时派生的
  副本),因此能力不降级:Program Files 被挪到其它盘也能正确拦截。
  winreg 延迟导入,POSIX 上模块仍可独立导入。
- 拒绝文案改指向 ra_config.json 白名单。

测试(tests/ 不跟踪,本地):
- 新增 tests/test_registry_scan_replica.py,发布前复刻 Registry 扫描规则
- test_publish_hygiene.py 的 ALLOWLIST / ALLOWLIST_EXPECTED_HITS 清空
- test_paths.py 删 3 个逃生开关用例,加注册表回退与「环境变量不再生效」回归

* fix: winreg 绑定为 Any,避免 Linux 上 mypy attr-defined 报错

CI 的 Mypy 在 ubuntu 上运行,winreg 是 Windows 专用模块,直接调用成员会报
Module has no attribute "OpenKey"。绑定到 Any 让两个平台的类型检查都成立。
2026-10-29 23:55:43 +08:00
StarAsh042 8c9c896a2c chore(release): 准备 5.2.0 文档与版本号 (#17)
* feat(core): stream CSV records and add a configurable read limit

CSV reading used to build a full List[List[str]] of the raw file before
parsing, so cold loads kept both the raw rows and the final objects in
memory at once. Records now stream straight into the parsers.

- artists: _read() feeds _iter_csv_rows() into parse_artist_rows() instead
  of _csv_rows(); a shared _iter_csv_rows() owns counting, early stop and
  error containment for both read paths.
- characters: build_records() is rewritten as a single pass and accepts
  Iterable. The previous Sequence signature iterated twice, so a generator
  input silently dropped the first data row (6 rows -> 4 records, no error).
- characters: load_records()/load_index() read via the new
  ArtistRepository.open_rows(), so the raw rows are no longer parked in
  _rows_cache. load_rows() stays as the public compatibility entry.
- limits: normalize_max_rows()/resolve_max_rows() accept only None or a
  positive integer. The effective limit is part of every cache key, and the
  count is defined once for both paths: csv.reader records, header and
  blank records included.
- exact-N no longer claims truncation: the streaming reader flags
  "truncated" only when it actually reaches record N+1, with no extra read.
- nodes: both take an optional csv_max_rows (default 0 = unlimited),
  appended last in optional and in the signature, marked advanced. ComfyUI
  1.55.18 restores widgets_values positionally (NamedValuesRestore is off
  by default), so appending at the end is what keeps old workflows intact.

Default behaviour is unchanged: same seeds produce byte-identical output
for both nodes.

* fix(core): tighten path boundaries and add administrator allowlists

Two boundary defects in ra_core/paths.py, plus an opt-in allowlist channel
for administrators who want to narrow the allowed roots.

- _resolve_unc_to_drive() used to map the admin-share form of ANY host to a
  local drive: \\evil-host\D$\data\x.csv became D:\data\x.csv and was then
  ALLOWED. The local path was what actually got opened, so the remote intent
  was silently downgraded to a local read/write. Only localhost, 127.0.0.1
  and [::1] are restored now; every other host stays UNC and is rejected by
  the existing UNC branch.
- _is_within() compared os.path.commonpath() against the root without
  normcase, so mixed-case nesting was judged outside. Under the existing
  blacklist this was unreachable (forbidden_roots() only keeps existing
  directories and realpath normalises their case), but an allowlist root that
  does not exist yet was rejected. Both sides are normcased now.
- New administrator allowlist, read from ra_config.json in the node directory
  (csv_allowed_roots / db_allowed_roots). No environment variable is read:
  reading one would add a python_environment_manipulation finding to the
  Registry scan, and the package is already flagged for the five existing
  reads. The file is absent by default, which keeps the current blacklist
  behaviour.
- The allowlist only narrows. Passing it still has to clear the UNC rejection
  and the forbidden-root loop. It is checked after _canonicalize() and before
  is_path_guard_disabled(), so "allowlist plus escape hatch" fails closed
  instead of the escape hatch silently winning.
- Invalid configuration fails closed with an actionable message: bad JSON,
  non-object top level, empty or missing lists, non-string roots, relative
  roots and bare drive letters are all rejected.
- for_write is no longer a dead parameter: it selects db_allowed_roots over
  csv_allowed_roots, so the two kinds stay separate.
- Node-directory data files are exempt: the bundled default CSV/DB are
  trusted runtime resources, and users sometimes write the full default path
  into custom_csv_path. Node source files are still blocked.

TOCTOU is unchanged and not claimed: check_db_path validates once and
sqlite3 reopens by path, and sqlite3 has no fd parameter.

* chore(release): prepare 5.2.0 docs and version bump

Documentation and version housekeeping for the 5.2.0 line. No runtime code
changes in this commit.

- CHANGELOG: new `## [未发布]` section covering the streaming CSV read, the
  configurable read limit, the two path-boundary fixes, the administrator
  allowlist and the publish-hygiene rule that had been missing. The section
  is renamed to `## [5.2.0] - <date>` at release time, per the existing
  discipline (every earlier release was made by renaming, not by adding).
- BENCHMARKS: same-machine old-vs-new comparison for the streaming rewrite
  (artist cold peak 13.30 -> 3.39 MB, character cold peak 27.26 -> 16.46 MB,
  character resident 26.86 -> 16.07 MB, character cold load 3129.7 ->
  2703.0 ms) and the effect of the limit (artist cold load 1545 -> 32.6 ms
  at max_rows=1000). Also indexes the behaviour-level regression tests that
  guard these, none of which assert absolute timings.
- README: test-suite size corrected to the measured values (304 test
  functions / 396 collected cases). The previous 254 / 338 had drifted.
- .gitignore: add *.egg-info/, build/, dist/, .pytest_cache/, .ruff_cache/
  and .mypy_cache/. `pip install -e ".[dev]"` creates
  ComfyUI_RollingArtist.egg-info/ in the repo root, which was not ignored,
  so a local install dirtied `git status` and broke the "diff matches plan"
  gate step.
- pyproject: version 5.1.7 -> 5.2.0.

Note: this branch is stacked on the two feature PRs; it only contains the
docs/version delta. Merging it before them would leave the changelog
describing code that is not on dev yet.
2026-10-28 20:51:01 +08:00
StarAsh042 bb8ddf38d5 chore(release): 5.1.7(文档类扫描触发面根治:CHANGELOG 排除出发布包)
- .comfyignore 根锚定排除 /CHANGELOG.md:5.1.5 finding 匹配实例证明
  扫描器把 CHANGELOG 历史段落中描述标准库 API 名称的字面量也按代码
  命中(7 条 finding 中 6 条源自 CHANGELOG 与迭代计划文档)
- CHANGELOG.md 历史段落 API 调用字样改写为描述性文字(纵深防御)
- 发布卫生测试增强(本地测试套件):tripwire 扫描从仅 .py 扩展到全部
  文本类文件(.py/.md/.json/.toml/.yml/.txt;CSV 数据文件不扫——
  角色名语料误触词面规则而扫描器不把数据文件当代码),CHANGELOG.md
  纳入发布包排除断言
2026-10-26 00:20:33 +08:00
StarAsh042 425a727708 chore(release): 5.1.6(扫描触发点二轮消除 + 发布卫生回归 + tests/docs 本地独享)
- db.py _write_schema_version:INSERT 语句提取为局部变量,
  断掉 database_operations 模式命中的 .execute( + 换行 + "INSERT INTO
  多行排版(SQL 与参数不变)
- paths.py:4 处系统目录探测 os.environ.get() → os.getenv();
  db.py 注释与 paths.py docstring 清除 sqlite3.connect() 字样(纵深防御)
- tests/test_publish_hygiene.py:发布包构成回归测试(本地测试套件新增),
  本次即靠它抓到 .comfyignore 无锚定 docs/ 误伤 web/docs/ 的真 bug,
  .comfyignore/.gitignore 均已改根锚定写法
- tests/ 与 docs/ 取消 git 跟踪转本地独享(.gitignore + .comfyignore),
  ci.yml test job 检出无 tests/ 时跳过而非失败
2026-10-26 00:07:43 +08:00
StarAsh042 1c6ed27ed6 chore(release): 5.1.5 2026-10-25 22:26:32 +08:00
StarAsh042 46db89de14 chore(release): 5.1.4 2026-10-25 21:55:04 +08:00
StarAsh042 b8506ebfea fix(registry): 等价 API 替换消除扫描误报(sqlite3.connect→sqlite3.Connection、os.environ.get→os.getenv)
- 行为零变化:338 个测试用例全过,门禁四件套全绿
- 目标:消除 Registry 安全扫描对 5.0.0-5.1.2 的 info 级误报 Flagged
- 兜底:如扫描器仍有其他触发模式,回退 registry-backend 人工申诉(稿已备)
2026-10-25 21:23:28 +08:00
StarAsh042 7d594708f9 chore(release): 5.1.2 2026-10-25 19:13:49 +08:00
StarAsh042 449f1478f9 chore(release): 5.1.1(工程与发布面收敛)
* feat(cache): 节点缓存键纳入 CSV 文件签名

ComfyUI 默认只按输入参数判断缓存是否命中。两个节点读的都是外部 CSV,
参数不变而文件被替换时缓存会一直返回旧结果,用户换了数据却看不到变化。

把 CSV 的「路径 + 修改时间 + 大小」作为 IS_CHANGED 的返回值纳入缓存键:
文件一变就重算;文件与参数都不变时指纹稳定,缓存行为与 5.0.0 一致。
路径被防护拒绝时返回稳定哨兵而不抛异常,避免打断 ComfyUI 的缓存判断。

* perf(character): 派生索引随记录缓存,单次执行 62.6ms -> 26.7ms

角色节点每次执行都在 3.4 万条记录上重建 record_map / names / 作品筛选下标,
CSV 没变时完全是重复劳动。把三者收进 CharacterIndex,与记录共用同一套
文件签名缓存(路径 + 列 + mtime + size),CSV 一变就重建。

顺带修掉作品下拉菜单的失效问题:菜单改为按默认 CSV 的文件签名缓存,
换 CSV 后自动重建,不再需要重启 ComfyUI。

实测(本机 7 次中位数):generate_characters 62.62ms -> 26.69ms(-57%)。
4 个 seed 下 prompt / characters_json / tested_count 逐字节一致。
剩余约 24ms 在 select_artists 的排除过滤里;改动它会改变 RNG 消耗顺序、
破坏同 seed 的可复现性,故本次不动。

* docs(db): 修正 generation_lock 的持锁范围说明

原文写「调用方应在进入本上下文之前完成抽样、权重计算、提示词拼装等纯 CPU 工作」,
但 RollingArtist 的重试循环必须待在事务里(命中已测组合要重新抽样),
实际持锁时长等于一次完整生成。文档与实现互相矛盾,读代码的人会误判并发行为。

改为如实描述:持锁时长由调用方决定,两种调用方式各自的代价是什么,
以及为什么不能简单把抽样挪到锁外(冲突只会推迟到写入时才被 INSERT OR IGNORE 吞掉)。
同时修正 __init__ 里「不覆盖事务内的业务计算」这句同样有歧义的注释。

纯注释改动,无代码行为变化。

* docs: 5.1.0 变更记录与 README 缓存/性能说明(T-04)

* docs: 归档 v5.1.0 迭代计划至 docs/iteration

* chore(release): 5.1.0

* docs: 回填 v5.1.0 执行记录至迭代计划

* docs: Sprint 2 文档三件套(决策登记/性能基线/测试口径)

* ci/chore: mypy job 非阻断 + .comfyignore 收敛发布面

* chore(release): 5.1.1
2026-10-25 18:35:17 +08:00
StarAsh042 dcb98bc58b chore(release): 5.1.0
* feat(cache): 节点缓存键纳入 CSV 文件签名

ComfyUI 默认只按输入参数判断缓存是否命中。两个节点读的都是外部 CSV,
参数不变而文件被替换时缓存会一直返回旧结果,用户换了数据却看不到变化。

把 CSV 的「路径 + 修改时间 + 大小」作为 IS_CHANGED 的返回值纳入缓存键:
文件一变就重算;文件与参数都不变时指纹稳定,缓存行为与 5.0.0 一致。
路径被防护拒绝时返回稳定哨兵而不抛异常,避免打断 ComfyUI 的缓存判断。

* perf(character): 派生索引随记录缓存,单次执行 62.6ms -> 26.7ms

角色节点每次执行都在 3.4 万条记录上重建 record_map / names / 作品筛选下标,
CSV 没变时完全是重复劳动。把三者收进 CharacterIndex,与记录共用同一套
文件签名缓存(路径 + 列 + mtime + size),CSV 一变就重建。

顺带修掉作品下拉菜单的失效问题:菜单改为按默认 CSV 的文件签名缓存,
换 CSV 后自动重建,不再需要重启 ComfyUI。

实测(本机 7 次中位数):generate_characters 62.62ms -> 26.69ms(-57%)。
4 个 seed 下 prompt / characters_json / tested_count 逐字节一致。
剩余约 24ms 在 select_artists 的排除过滤里;改动它会改变 RNG 消耗顺序、
破坏同 seed 的可复现性,故本次不动。

* docs(db): 修正 generation_lock 的持锁范围说明

原文写「调用方应在进入本上下文之前完成抽样、权重计算、提示词拼装等纯 CPU 工作」,
但 RollingArtist 的重试循环必须待在事务里(命中已测组合要重新抽样),
实际持锁时长等于一次完整生成。文档与实现互相矛盾,读代码的人会误判并发行为。

改为如实描述:持锁时长由调用方决定,两种调用方式各自的代价是什么,
以及为什么不能简单把抽样挪到锁外(冲突只会推迟到写入时才被 INSERT OR IGNORE 吞掉)。
同时修正 __init__ 里「不覆盖事务内的业务计算」这句同样有歧义的注释。

纯注释改动,无代码行为变化。

* docs: 5.1.0 变更记录与 README 缓存/性能说明(T-04)

* docs: 归档 v5.1.0 迭代计划至 docs/iteration

* chore(release): 5.1.0
2026-10-25 15:57:40 +08:00
StarAsh042 83076a936b chore(release): 5.0.0
工程化基线版本:CI、测试入库与打包配置齐备,README 与 CHANGELOG 同步到 5.0.0。
2026-10-22 01:12:00 +08:00
StarAsh042 2ab9aba9e7 build: 补齐 setuptools 打包配置
flat-layout 下 setuptools 会同时发现 web / ra_core / locales 三个顶层包并拒绝构建,
导致 `pip install -e .` 直接失败。显式声明只打包 ra_core;
节点文件由 ComfyUI 以「目录即包」加载,不需要进入 wheel。
2026-10-20 09:12:00 +08:00
StarAsh042 b651ff5629 chore(release): 4.0.0
数据加工工具链与 LFS 存储上线,README 与 CHANGELOG 同步到 4.0.0。
2026-10-19 15:12:00 +08:00
StarAsh042 b057c5c7b9 chore(release): 3.0.0
界面本地化与帮助文档上线,README 与 CHANGELOG 同步到 3.0.0。
2026-10-18 15:12:00 +08:00
StarAsh042 7d1674f7c4 chore(release): 2.0.0
角色节点上线,README 与 CHANGELOG 同步到 2.0.0。
2026-10-17 15:12:00 +08:00
StarAsh042 4cb3b3b690 chore(release): 1.0.0
首个发布版本:内核与画师节点可用。
2026-10-15 15:12:00 +08:00