16 Commits
Author SHA1 Message Date
StarAsh042 83e54f76f6 fix(core): 收紧路径边界并新增管理员白名单 (#16)
* 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.
2026-10-28 20:48:04 +08:00
StarAsh042 0f90556aa3 feat(core): 流式读取 CSV 并新增可配置读取上限 (#15)
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.
2026-10-28 20:46:12 +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 61db2818b0 docs(t-17-t-18): 仓库清理与数据更新 SOP
- T-17: 删除本地备份分支 backup/pre-rebuild-20261022-233943(重建前平行历史,
  内容已被 5.0.0 完整包含,删除前经 git diff 树对比核验零损失,从未推送 origin);
  import_legacy_csv 补生命周期标注(4.x→5.0.0 一次性迁移工具,无生产调用方)
- T-18: 新增 docs/DATA-UPDATE-SOP.md(取数/LFS 纪律/加工参数/校验清单/回滚/提交约定),
  README 数据预处理段挂链接
- 不动 pyproject 版本号,合并不触发 Registry 发布
2026-10-25 22:12:20 +08:00
StarAsh042 5fb57f9e3a feat(character): T-16 作品筛选执行期一致性(报错分级+copyrights 缓存+边界说明)
- CharacterIndex 新增 copyrights 惰性缓存属性(与下拉同一排序)
- 作品筛选为空的报错分级:自定义 CSV 场景指明「不在自定义 CSV 的作品列表中」并给出该 CSV 总量与热门示例;默认场景指明「不在默认 CSV 的作品列表中」
- copyright_pick / custom_csv_path tooltip 与 README 补齐联动方向
- locales zh/en 同步;新增 4 个测试(含报错消息断言)
- 背景:ComfyUI INPUT_TYPES 无参调用拿不到实例参数,下拉联动架构上不可行;
  执行期筛选本就作用于 custom_csv_path,本版补齐错误指引与边界说明
- 不动 pyproject 版本号(等 5.1.3 定级后发 5.1.4)
2026-10-25 21:46:36 +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 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 da16f976ff feat(nodes): 新增 RollingCharacter 节点
角色节点输出不带权重的提示词,支持作品筛选与角色描述开关;
两个节点共用 ra_core,节点层不重复实现解析逻辑。
2026-10-16 09:12:00 +08:00
StarAsh042 8db4c37d16 feat(core): 角色标签解析与作品筛选
角色数据的 trigger 是「作品 + 角色 + 外观标签」的混合串,
先拆解成结构化记录,才能按作品筛选、按需拼装描述。
2026-10-16 09:12:00 +08:00
StarAsh042 9e8d735c22 feat(nodes): RollingArtist 节点与包注册
节点层只做参数规范化、调用内核、组装输出三件事;
NODE_CLASS_MAPPINGS 的 key 一旦发布即是对外契约。
2026-10-14 15:12:00 +08:00
StarAsh042 eb4f190c87 feat(core): 画师抽样与提示词组装
索引化抽样把大 CSV 的取人从 O(n²) 降到 O(k),并按权重曲线组装提示词。
2026-10-14 09:12:00 +08:00
StarAsh042 b07d41aaaf feat(db): SQLite 记录库与事务内判重
判重从「读 CSV 再比对」改成索引查询,判重、生成、记录跑在同一个
BEGIN IMMEDIATE 事务里,多开 ComfyUI 也不会重复出图。
2026-10-13 15:12:00 +08:00
StarAsh042 8e27b3dfc6 feat(security): 路径防护与受保护目录
自定义 CSV / 数据库路径原样透传等于把任意读写交给工作流作者。
命中系统目录、凭据目录、ComfyUI 自身代码或其它节点目录时直接拒绝,
并规范化 Windows 扩展前缀、ADS 与 UNC 管理共享写法。
2026-10-13 09:12:00 +08:00
StarAsh042 716e6a5d48 feat(core): 启动期运行产物清理
ComfyUI 启动时清掉上一轮遗留的默认数据库与旧版 CSV 记录,
避免带着脏状态进入新会话。
2026-10-13 15:12:00 +08:00
StarAsh042 1042e4d1df feat(core): CSV 读取与参数归一化
CSV 读取要处理编码、列识别与旧版记录解析;参数归一化负责把界面上
可能传来的字符串容错转成 int / float / bool。
2026-10-12 09:12:00 +08:00
StarAsh042 773f06a370 feat(core): 引入常量与权重分配算法
把权重上下限、步长与三条权重曲线(flat / dominant / ramp)先定下来,
节点层只负责把参数传进来,不参与算法。
2026-10-12 15:12:00 +08:00