bensz-skill-kernel 0.14.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. bensz_skill_kernel-0.14.1/LICENSE +21 -0
  2. bensz_skill_kernel-0.14.1/MANIFEST.in +5 -0
  3. bensz_skill_kernel-0.14.1/PKG-INFO +159 -0
  4. bensz_skill_kernel-0.14.1/README.md +119 -0
  5. bensz_skill_kernel-0.14.1/pyproject.toml +43 -0
  6. bensz_skill_kernel-0.14.1/setup.cfg +4 -0
  7. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/__init__.py +159 -0
  8. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/atomic_verifiers.py +93 -0
  9. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/builtins.py +114 -0
  10. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/cli.py +640 -0
  11. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/contract_packs.py +864 -0
  12. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/contracts.py +121 -0
  13. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/packs.py +215 -0
  14. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/runtime.py +1090 -0
  15. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/state_ids.py +39 -0
  16. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/active/STATE.md +42 -0
  17. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/cancelled/STATE.md +32 -0
  18. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/checking/STATE.md +45 -0
  19. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/completed/STATE.md +35 -0
  20. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/delivering/STATE.md +43 -0
  21. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/failed/STATE.md +33 -0
  22. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/index.json +16 -0
  23. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/planned/STATE.md +41 -0
  24. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/waiting/STATE.md +41 -0
  25. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/workspace-closed/STATE.md +37 -0
  26. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states/workspace-ready/STATE.md +42 -0
  27. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/states.py +706 -0
  28. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifier_ids.py +34 -0
  29. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/artifact-file-exists/VERIFIER.md +4 -0
  30. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/artifact-file-exists/scripts/verify.py +24 -0
  31. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/citation-truth-and-fit/VERIFIER.md +6 -0
  32. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/contract-conformance/VERIFIER.md +3 -0
  33. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/contract-conformance/scripts/verify.py +5 -0
  34. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/diff-scope/VERIFIER.md +3 -0
  35. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/diff-scope/scripts/verify.py +5 -0
  36. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/event-integrity/VERIFIER.md +3 -0
  37. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/event-integrity/scripts/verify.py +5 -0
  38. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/evidence-provenance/VERIFIER.md +3 -0
  39. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/evidence-provenance/scripts/verify.py +5 -0
  40. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/index.json +18 -0
  41. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/markdown-link-integrity/VERIFIER.md +8 -0
  42. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/markdown-link-integrity/scripts/collector.py +261 -0
  43. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/markdown-link-integrity/scripts/verify.py +47 -0
  44. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/path-scope/VERIFIER.md +3 -0
  45. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/path-scope/scripts/verify.py +5 -0
  46. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/schema-conformance/VERIFIER.md +3 -0
  47. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/schema-conformance/scripts/verify.py +5 -0
  48. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/secret-redaction/VERIFIER.md +3 -0
  49. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/secret-redaction/scripts/verify.py +5 -0
  50. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/state-transition/VERIFIER.md +3 -0
  51. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/state-transition/scripts/verify.py +5 -0
  52. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/task-completeness/VERIFIER.md +3 -0
  53. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers/task-completeness/scripts/verify.py +5 -0
  54. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/verifiers.py +896 -0
  55. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel/workspace.py +269 -0
  56. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel.egg-info/PKG-INFO +159 -0
  57. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel.egg-info/SOURCES.txt +59 -0
  58. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel.egg-info/dependency_links.txt +1 -0
  59. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel.egg-info/entry_points.txt +2 -0
  60. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel.egg-info/requires.txt +1 -0
  61. bensz_skill_kernel-0.14.1/src/bensz_skill_kernel.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bensz Conan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ global-exclude *.py[cod]
2
+ global-exclude __pycache__
3
+ global-exclude .DS_Store
4
+ prune .pytest_cache
5
+ prune .ruff_cache
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: bensz-skill-kernel
3
+ Version: 0.14.1
4
+ Summary: Append-only lifecycle runtime for Bensz Agent Skills
5
+ Author: Bensz Conan
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Bensz Conan
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/huangwb8/skills
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: Programming Language :: Python :: 3.11
31
+ Classifier: Programming Language :: Python :: 3.12
32
+ Classifier: Programming Language :: Python :: 3.13
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Operating System :: OS Independent
35
+ Requires-Python: >=3.11
36
+ Description-Content-Type: text/markdown
37
+ License-File: LICENSE
38
+ Requires-Dist: PyYAML<7,>=6.0
39
+ Dynamic: license-file
40
+
41
+ # bensz-skill-kernel
42
+
43
+ 轻量的 Agent Skill 状态、工作区与 verifier 生命周期内核。
44
+
45
+ ## Python 支持
46
+
47
+ - 最低支持版本:Python 3.11。
48
+ - 已验证测试矩阵:Python 3.11、3.12、3.13。
49
+ - 推荐运行版本:Python 3.12。
50
+
51
+ 内核运行时除 PyYAML(用于读取 Skill 的 `config.yaml`)外仅依赖 Python 标准库;新 Python 版本会在测试矩阵验证后纳入官方支持范围。
52
+
53
+ State 与 Verifier 都采用目录化 Contract Pack:一个 Markdown 契约、索引元数据和零个或多个执行组件。`contract_packs.py` 在 `packs.py` 的发现与 JSON-stdio 边界之上统一描述并编排 `script`、`agent`、`human` 组件,绑定契约/计划/组件哈希、证据、依赖顺序、run/attempt 和执行者。State 仍由状态图/迁移适配器解释组件结果,Verifier 仍由 verdict/Gate 适配器解释组件结果,二者不会因共享执行层而混淆语义。
54
+
55
+ Verifier ID 的 canonical 命名、版本和 alias 迁移规则见仓库级 [`docs/verifier-id-naming.md`](../../docs/verifier-id-naming.md);State ID 对应规则见 [`docs/state-id-naming.md`](../../docs/state-id-naming.md)。
56
+
57
+ 状态定义采用目录化协议:`states/index.json` 是 State 包目录清单和属性索引,每个元状态目录包含一个 `STATE.md`,可选附带 JSON-stdio
58
+ 检查或演示脚本。内置状态可以通过统一命令发现;`--root` 会在内置状态之外叠加 Skill
59
+ 状态包,而非替换它:
60
+
61
+ ```bash
62
+ bsk state list
63
+ bsk state describe bensz.workspace.ready
64
+ bsk state list --root path/to/skill/states
65
+ ```
66
+
67
+ Kernel 自己的八个生命周期状态均直接位于 `states/<state>/STATE.md`:`planned`、
68
+ `active`、`waiting`、`checking`、`delivering`、`completed`、`failed`、`cancelled`。
69
+ 目录契约用于发现与人工审核,`runtime.py` 的事件 reducer 是可执行转移语义;测试会阻止两者漂移。
70
+ `states/workspace-ready/` 与 `states/workspace-closed/` 保存工作区系统状态。领域 Skill 的业务阶段仍在自身 `references/states/`,
71
+ 不进入 kernel 生命周期目录。
72
+
73
+ 一个 Skill 现在在根目录 `config.yaml` 的 `runtime` 节声明它采用哪些状态包、初始状态、可用状态
74
+ 和 Verifier 子集;旧版 `state-machine.json` 仍可只读兼容。kernel 只校验该格式,不理解领域动作。
75
+ Agent 先读取状态契约,再用同一命令检查或持久化转移。旧 Pack 的单一 `entrypoint` 继续按原 JSON-stdio 协议执行;新 Pack 可在索引中声明组件计划。required 组件只有全部完成并通过时才允许对应阶段条件成立:
76
+
77
+ ```bash
78
+ bsk state list --skill-root path/to/skill
79
+ bsk state describe bensz.workspace.ready --skill-root path/to/skill
80
+ bsk state check bensz.workspace.ready org.example.skill.collecting --skill-root path/to/skill
81
+ bsk state transition .bensz-api/task-YYYYMMDD-HHMM-demo skill-name org.example.skill.collecting \
82
+ --skill-root path/to/skill --context-json '{"input":"report.md"}'
83
+ ```
84
+
85
+ 状态操作统一返回 `bensz-meta-state-v1` JSON,包含操作、状态、结果、可选脚本回执和
86
+ 持久化快照。每个 Skill 的当前元状态写入自身 `log/meta-state.json`;它与任务级
87
+ `events.ndjson` / `state.json` 分层,后者仍只记录生命周期、验证与交付事实。
88
+ 成功的 Skill 状态转移同时以 `state.transition`(`state_domain: skill`)追加到任务事件账本,
89
+ `bsk rebuild` 会将其投影到 `skill_states`/`skill_state_transitions`,并核验最新元状态快照的稳定字段哈希;快照缺失时仍可据事件账本恢复,漂移则返回 `integrity_error`。
90
+
91
+ 状态 `invariants` 默认是面向领域的说明;Kernel 只执行有明确协议的通用 invariant。
92
+ 当前支持 `verifier-result-recorded`:离开声明该 invariant 的状态前,任务事件账本必须
93
+ 同时包含 `verification.result` 和 `verification.gate`。未满足时 CLI 返回结构化
94
+ `rejected`,不会写入新的状态快照;领域专属 invariant 仍由 Skill helper 或人工复核负责。
95
+ 事件带有运行身份时,`run_id` 与 `attempt_id` 必须成对传入,避免重试之间串用验证证据。
96
+
97
+ 内置 State 的 ID、版本、kind、aliases、classification、tags、`mode`、`assurance_tier` 和 `components` 由 `states/index.json` 单独管理;
98
+ `STATE.md` 只保留 `description`、`entry_conditions`、`invariants`、`transitions` 等状态工作契约及正文说明。
99
+ 没有 `index.json` 的外部兼容目录仍可在 `STATE.md` frontmatter 声明完整属性。可选 `entrypoint` 是相对于该 `STATE.md` 目录的脚本:stdin 接收
100
+ `{"protocol":"bensz-meta-state-v1","state":...,"request":...}`,stdout 只能输出一个
101
+ JSON 对象,其中 `verdict` 是 `pass`、`fail`、`uncertain`、`unchecked`、`error`、
102
+ `timed_out` 或 `skipped`,并可带 `summary`、`facts`、`evidence_refs`。只有成功执行且
103
+ `verdict=pass` 的 helper 会允许持久化转移。显式 `agent`/`human` 组件会先返回带契约、组件、计划和运行身份的 handoff;外部执行者按 handoff 回传标准组件结果后,`StateContractAdapter` 才会把公共结果解释为阶段是否满足。没有新组件声明的旧 instruction-only 状态仍保持兼容行为。
104
+
105
+ 每个逻辑任务先初始化一个不可变的 BenszAPI 工作区;Skill 不应自行拼接路径,而应通过
106
+ 工作区命令取得自己的 `input`、`output` 和 `log` 边界:
107
+
108
+ ```bash
109
+ bsk workspace init . --description citation-review
110
+ bsk workspace path .bensz-api/task-YYYYMMDD-HHMM-citation-review validate-md-ref input
111
+ bsk workspace status .bensz-api/task-YYYYMMDD-HHMM-citation-review
112
+ ```
113
+
114
+ 初始化结果中的 `bensz.workspace.ready` 是所有 Skill 状态的系统级前置状态,旧 ID `workspace.ready` 作为 alias 保留。工作区还会创建
115
+ 共享 `shared/input|output|log` 边界;工作区清单保存协议版本和初始状态,事件账本仍由
116
+ 下方的生命周期命令追加和重放;两者保持分层,避免把 Skill 领域状态硬编码进核心 reducer。
117
+
118
+ 安装后,Skill 通过 `bsk` 发现和调用包内 `bensz_skill_kernel/verifiers/` 目录中的 verifier。`verifiers/index.json` 是 Verifier 包目录清单和执行计划的单一来源;每个 verifier 由一个 `VERIFIER.md` 契约和 `components` 组成。脚本组件通过 stdin/stdout 交换 JSON,Agent/人工组件由 Kernel 准备 handoff、由外部宿主执行并回传绑定结果:
119
+
120
+ ```bash
121
+ bsk verifier list --tag citation
122
+ bsk verifier describe bensz.evidence.citation-truth-fit --version 1.0.0
123
+ bsk verifier run bensz.document.markdown-link-integrity --input README.md
124
+ ```
125
+
126
+ 内置 Verifier 的 ID、版本、aliases、classification、tags、契约路径、mode、assurance 和组件由
127
+ `verifiers/index.json` 单独管理;`VERIFIER.md` 专注判断目标、证据边界与执行说明。
128
+ 没有 `index.json` 的外部兼容目录仍可使用原 YAML frontmatter;旧单入口 Pack 会兼容解释为一个脚本组件,旧 instruction-only Pack 会给出缺少显式组件元数据的诊断。脚本入口遵循:stdin 一个请求 JSON,stdout
129
+ 一个结果 JSON(`verdict` 为 `pass`、`fail`、`uncertain`、`unchecked`、`error`、
130
+ `timed_out` 或 `skipped`)。kernel 负责超时、异常、非法 JSON 和结果字段归一化。
131
+
132
+ 当前内置示例:
133
+
134
+ - `bensz.artifact.file-existence`:确认本地产物是现有普通文件;旧 ID `artifact.file-exists` 作为 alias 保留。
135
+ - `bensz.document.markdown-link-integrity`:Markdown 链接和锚点完整性检查,标签 `common`、`markdown`、`links`、`deterministic`;旧 ID `markdown.link-integrity`、`markdown.references` 作为 alias 保留。
136
+ - `bensz.evidence.citation-truth-fit`:格式无关的引用真实性与适切性契约;索引将它显式声明为 `agent` 组件,Kernel 不捆绑模型,未回传绑定结果时保持 `unchecked`/`wait`;旧 ID `citation.truth-and-fit` 作为 alias 保留。
137
+
138
+ 首批通用原子 Pack 还包括合同一致性、路径范围、Schema、diff 范围、敏感信息脱敏、证据来源、事件完整性、状态转移和任务完整性。每个 Pack 都位于 `verifiers/<slug>/`,包含可审查的 `VERIFIER.md` 与可选 `scripts/verify.py`;共享实现位于 kernel 模块层的 `atomic_verifiers.py`,不占用 Verifier 清单目录,也不再把规则主体塞进 `builtins.py`。它们只接受通用事实,不包含领域规则。`index.json` 是属性的单一来源;注册表会校验其目录、契约和入口真实存在,且没有漏列或陈旧条目。
139
+
140
+ 代码只定义发现、调用、超时、结果归一化和事件记录协议,不内置领域判断流程。Markdown、LaTeX、Word 等格式适配器可各自选择适用 verifier。
141
+
142
+ 需要审计时,为 `run` 增加 `--events EVENTS --run-id RUN_ID`;命令会输出统一 `results`、`gate` 和兼容的 `verification` 字段。Agent/人工组件未执行时还会在顶层输出完整 handoff,但 handoff 不进入持久化 `results`,避免把契约正文或原始上下文写入事件账本。Python Adapter 使用 `FilesystemVerifierRegistry.run_contract()` 接收外部组件提交。生命周期底层命令仍可通过 `bsk status/rebuild/append/transition/artifact/validation/delivery` 使用。
143
+
144
+ # 运行边界与审计
145
+
146
+ Pack helper 默认以受信本地进程运行,但内核会限制输入、stdout/stderr 体积、环境变量和执行时长,并在超时后终止整个进程组;调用不可信 Pack 时应显式传入 `trusted=False`,此时会 fail-closed。该边界是进程级资源与路径约束,不等同于容器或操作系统沙箱。
147
+
148
+ 事件账本除状态投影外还保留可选的运行契约快照、授权链和执行审计轨迹;`reduce_events()` 仅用于离线状态投影重放,不会重新调用模型或工具。`verification-v2` 结果会在记录和完成门禁处复核组件唯一性、哈希、证据引用、run/attempt、执行者/模型及人工确认,调用方自报的 aggregate pass 不能覆盖 required 失败或漏跑。`summarize_metrics()` 额外汇总组件绑定率和执行者身份覆盖率。
149
+
150
+ ## 发布到 PyPI
151
+
152
+ 仓库根目录的发布助手默认只构建 wheel/sdist 并运行 `twine check`;只有显式增加 `--upload` 才会使用 Twine 的标准本机鉴权上传到正式 PyPI:
153
+
154
+ ```bash
155
+ python3 tests/publish_bsk_pypi.py
156
+ python3 tests/publish_bsk_pypi.py --upload
157
+ ```
158
+
159
+ 构建产物保存在 `tmp/bsk-pypi/`,脚本不会读取、复制或记录 PyPI 凭据。
@@ -0,0 +1,119 @@
1
+ # bensz-skill-kernel
2
+
3
+ 轻量的 Agent Skill 状态、工作区与 verifier 生命周期内核。
4
+
5
+ ## Python 支持
6
+
7
+ - 最低支持版本:Python 3.11。
8
+ - 已验证测试矩阵:Python 3.11、3.12、3.13。
9
+ - 推荐运行版本:Python 3.12。
10
+
11
+ 内核运行时除 PyYAML(用于读取 Skill 的 `config.yaml`)外仅依赖 Python 标准库;新 Python 版本会在测试矩阵验证后纳入官方支持范围。
12
+
13
+ State 与 Verifier 都采用目录化 Contract Pack:一个 Markdown 契约、索引元数据和零个或多个执行组件。`contract_packs.py` 在 `packs.py` 的发现与 JSON-stdio 边界之上统一描述并编排 `script`、`agent`、`human` 组件,绑定契约/计划/组件哈希、证据、依赖顺序、run/attempt 和执行者。State 仍由状态图/迁移适配器解释组件结果,Verifier 仍由 verdict/Gate 适配器解释组件结果,二者不会因共享执行层而混淆语义。
14
+
15
+ Verifier ID 的 canonical 命名、版本和 alias 迁移规则见仓库级 [`docs/verifier-id-naming.md`](../../docs/verifier-id-naming.md);State ID 对应规则见 [`docs/state-id-naming.md`](../../docs/state-id-naming.md)。
16
+
17
+ 状态定义采用目录化协议:`states/index.json` 是 State 包目录清单和属性索引,每个元状态目录包含一个 `STATE.md`,可选附带 JSON-stdio
18
+ 检查或演示脚本。内置状态可以通过统一命令发现;`--root` 会在内置状态之外叠加 Skill
19
+ 状态包,而非替换它:
20
+
21
+ ```bash
22
+ bsk state list
23
+ bsk state describe bensz.workspace.ready
24
+ bsk state list --root path/to/skill/states
25
+ ```
26
+
27
+ Kernel 自己的八个生命周期状态均直接位于 `states/<state>/STATE.md`:`planned`、
28
+ `active`、`waiting`、`checking`、`delivering`、`completed`、`failed`、`cancelled`。
29
+ 目录契约用于发现与人工审核,`runtime.py` 的事件 reducer 是可执行转移语义;测试会阻止两者漂移。
30
+ `states/workspace-ready/` 与 `states/workspace-closed/` 保存工作区系统状态。领域 Skill 的业务阶段仍在自身 `references/states/`,
31
+ 不进入 kernel 生命周期目录。
32
+
33
+ 一个 Skill 现在在根目录 `config.yaml` 的 `runtime` 节声明它采用哪些状态包、初始状态、可用状态
34
+ 和 Verifier 子集;旧版 `state-machine.json` 仍可只读兼容。kernel 只校验该格式,不理解领域动作。
35
+ Agent 先读取状态契约,再用同一命令检查或持久化转移。旧 Pack 的单一 `entrypoint` 继续按原 JSON-stdio 协议执行;新 Pack 可在索引中声明组件计划。required 组件只有全部完成并通过时才允许对应阶段条件成立:
36
+
37
+ ```bash
38
+ bsk state list --skill-root path/to/skill
39
+ bsk state describe bensz.workspace.ready --skill-root path/to/skill
40
+ bsk state check bensz.workspace.ready org.example.skill.collecting --skill-root path/to/skill
41
+ bsk state transition .bensz-api/task-YYYYMMDD-HHMM-demo skill-name org.example.skill.collecting \
42
+ --skill-root path/to/skill --context-json '{"input":"report.md"}'
43
+ ```
44
+
45
+ 状态操作统一返回 `bensz-meta-state-v1` JSON,包含操作、状态、结果、可选脚本回执和
46
+ 持久化快照。每个 Skill 的当前元状态写入自身 `log/meta-state.json`;它与任务级
47
+ `events.ndjson` / `state.json` 分层,后者仍只记录生命周期、验证与交付事实。
48
+ 成功的 Skill 状态转移同时以 `state.transition`(`state_domain: skill`)追加到任务事件账本,
49
+ `bsk rebuild` 会将其投影到 `skill_states`/`skill_state_transitions`,并核验最新元状态快照的稳定字段哈希;快照缺失时仍可据事件账本恢复,漂移则返回 `integrity_error`。
50
+
51
+ 状态 `invariants` 默认是面向领域的说明;Kernel 只执行有明确协议的通用 invariant。
52
+ 当前支持 `verifier-result-recorded`:离开声明该 invariant 的状态前,任务事件账本必须
53
+ 同时包含 `verification.result` 和 `verification.gate`。未满足时 CLI 返回结构化
54
+ `rejected`,不会写入新的状态快照;领域专属 invariant 仍由 Skill helper 或人工复核负责。
55
+ 事件带有运行身份时,`run_id` 与 `attempt_id` 必须成对传入,避免重试之间串用验证证据。
56
+
57
+ 内置 State 的 ID、版本、kind、aliases、classification、tags、`mode`、`assurance_tier` 和 `components` 由 `states/index.json` 单独管理;
58
+ `STATE.md` 只保留 `description`、`entry_conditions`、`invariants`、`transitions` 等状态工作契约及正文说明。
59
+ 没有 `index.json` 的外部兼容目录仍可在 `STATE.md` frontmatter 声明完整属性。可选 `entrypoint` 是相对于该 `STATE.md` 目录的脚本:stdin 接收
60
+ `{"protocol":"bensz-meta-state-v1","state":...,"request":...}`,stdout 只能输出一个
61
+ JSON 对象,其中 `verdict` 是 `pass`、`fail`、`uncertain`、`unchecked`、`error`、
62
+ `timed_out` 或 `skipped`,并可带 `summary`、`facts`、`evidence_refs`。只有成功执行且
63
+ `verdict=pass` 的 helper 会允许持久化转移。显式 `agent`/`human` 组件会先返回带契约、组件、计划和运行身份的 handoff;外部执行者按 handoff 回传标准组件结果后,`StateContractAdapter` 才会把公共结果解释为阶段是否满足。没有新组件声明的旧 instruction-only 状态仍保持兼容行为。
64
+
65
+ 每个逻辑任务先初始化一个不可变的 BenszAPI 工作区;Skill 不应自行拼接路径,而应通过
66
+ 工作区命令取得自己的 `input`、`output` 和 `log` 边界:
67
+
68
+ ```bash
69
+ bsk workspace init . --description citation-review
70
+ bsk workspace path .bensz-api/task-YYYYMMDD-HHMM-citation-review validate-md-ref input
71
+ bsk workspace status .bensz-api/task-YYYYMMDD-HHMM-citation-review
72
+ ```
73
+
74
+ 初始化结果中的 `bensz.workspace.ready` 是所有 Skill 状态的系统级前置状态,旧 ID `workspace.ready` 作为 alias 保留。工作区还会创建
75
+ 共享 `shared/input|output|log` 边界;工作区清单保存协议版本和初始状态,事件账本仍由
76
+ 下方的生命周期命令追加和重放;两者保持分层,避免把 Skill 领域状态硬编码进核心 reducer。
77
+
78
+ 安装后,Skill 通过 `bsk` 发现和调用包内 `bensz_skill_kernel/verifiers/` 目录中的 verifier。`verifiers/index.json` 是 Verifier 包目录清单和执行计划的单一来源;每个 verifier 由一个 `VERIFIER.md` 契约和 `components` 组成。脚本组件通过 stdin/stdout 交换 JSON,Agent/人工组件由 Kernel 准备 handoff、由外部宿主执行并回传绑定结果:
79
+
80
+ ```bash
81
+ bsk verifier list --tag citation
82
+ bsk verifier describe bensz.evidence.citation-truth-fit --version 1.0.0
83
+ bsk verifier run bensz.document.markdown-link-integrity --input README.md
84
+ ```
85
+
86
+ 内置 Verifier 的 ID、版本、aliases、classification、tags、契约路径、mode、assurance 和组件由
87
+ `verifiers/index.json` 单独管理;`VERIFIER.md` 专注判断目标、证据边界与执行说明。
88
+ 没有 `index.json` 的外部兼容目录仍可使用原 YAML frontmatter;旧单入口 Pack 会兼容解释为一个脚本组件,旧 instruction-only Pack 会给出缺少显式组件元数据的诊断。脚本入口遵循:stdin 一个请求 JSON,stdout
89
+ 一个结果 JSON(`verdict` 为 `pass`、`fail`、`uncertain`、`unchecked`、`error`、
90
+ `timed_out` 或 `skipped`)。kernel 负责超时、异常、非法 JSON 和结果字段归一化。
91
+
92
+ 当前内置示例:
93
+
94
+ - `bensz.artifact.file-existence`:确认本地产物是现有普通文件;旧 ID `artifact.file-exists` 作为 alias 保留。
95
+ - `bensz.document.markdown-link-integrity`:Markdown 链接和锚点完整性检查,标签 `common`、`markdown`、`links`、`deterministic`;旧 ID `markdown.link-integrity`、`markdown.references` 作为 alias 保留。
96
+ - `bensz.evidence.citation-truth-fit`:格式无关的引用真实性与适切性契约;索引将它显式声明为 `agent` 组件,Kernel 不捆绑模型,未回传绑定结果时保持 `unchecked`/`wait`;旧 ID `citation.truth-and-fit` 作为 alias 保留。
97
+
98
+ 首批通用原子 Pack 还包括合同一致性、路径范围、Schema、diff 范围、敏感信息脱敏、证据来源、事件完整性、状态转移和任务完整性。每个 Pack 都位于 `verifiers/<slug>/`,包含可审查的 `VERIFIER.md` 与可选 `scripts/verify.py`;共享实现位于 kernel 模块层的 `atomic_verifiers.py`,不占用 Verifier 清单目录,也不再把规则主体塞进 `builtins.py`。它们只接受通用事实,不包含领域规则。`index.json` 是属性的单一来源;注册表会校验其目录、契约和入口真实存在,且没有漏列或陈旧条目。
99
+
100
+ 代码只定义发现、调用、超时、结果归一化和事件记录协议,不内置领域判断流程。Markdown、LaTeX、Word 等格式适配器可各自选择适用 verifier。
101
+
102
+ 需要审计时,为 `run` 增加 `--events EVENTS --run-id RUN_ID`;命令会输出统一 `results`、`gate` 和兼容的 `verification` 字段。Agent/人工组件未执行时还会在顶层输出完整 handoff,但 handoff 不进入持久化 `results`,避免把契约正文或原始上下文写入事件账本。Python Adapter 使用 `FilesystemVerifierRegistry.run_contract()` 接收外部组件提交。生命周期底层命令仍可通过 `bsk status/rebuild/append/transition/artifact/validation/delivery` 使用。
103
+
104
+ # 运行边界与审计
105
+
106
+ Pack helper 默认以受信本地进程运行,但内核会限制输入、stdout/stderr 体积、环境变量和执行时长,并在超时后终止整个进程组;调用不可信 Pack 时应显式传入 `trusted=False`,此时会 fail-closed。该边界是进程级资源与路径约束,不等同于容器或操作系统沙箱。
107
+
108
+ 事件账本除状态投影外还保留可选的运行契约快照、授权链和执行审计轨迹;`reduce_events()` 仅用于离线状态投影重放,不会重新调用模型或工具。`verification-v2` 结果会在记录和完成门禁处复核组件唯一性、哈希、证据引用、run/attempt、执行者/模型及人工确认,调用方自报的 aggregate pass 不能覆盖 required 失败或漏跑。`summarize_metrics()` 额外汇总组件绑定率和执行者身份覆盖率。
109
+
110
+ ## 发布到 PyPI
111
+
112
+ 仓库根目录的发布助手默认只构建 wheel/sdist 并运行 `twine check`;只有显式增加 `--upload` 才会使用 Twine 的标准本机鉴权上传到正式 PyPI:
113
+
114
+ ```bash
115
+ python3 tests/publish_bsk_pypi.py
116
+ python3 tests/publish_bsk_pypi.py --upload
117
+ ```
118
+
119
+ 构建产物保存在 `tmp/bsk-pypi/`,脚本不会读取、复制或记录 PyPI 凭据。
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "bensz-skill-kernel"
7
+ version = "0.14.1"
8
+ description = "Append-only lifecycle runtime for Bensz Agent Skills"
9
+ readme = {file = "README.md", content-type = "text/markdown"}
10
+ license = {file = "LICENSE"}
11
+ authors = [{name = "Bensz Conan"}]
12
+ requires-python = ">=3.11"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+ dependencies = [
22
+ "PyYAML>=6.0,<7",
23
+ ]
24
+
25
+ [project.scripts]
26
+ bsk = "bensz_skill_kernel.runtime:main"
27
+
28
+ [project.urls]
29
+ Repository = "https://github.com/huangwb8/skills"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+ include = ["bensz_skill_kernel*"]
34
+
35
+ [tool.setuptools.package-data]
36
+ bensz_skill_kernel = ["states/*", "states/**/*", "verifiers/*", "verifiers/**/*"]
37
+
38
+ [tool.setuptools.exclude-package-data]
39
+ "*" = ["__pycache__/*", "*.pyc", "**/__pycache__/*", "**/*.pyc"]
40
+
41
+ [tool.pytest.ini_options]
42
+ testpaths = ["tests"]
43
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,159 @@
1
+ """Minimal, append-only runtime kernel for Agent Skill task lifecycles."""
2
+
3
+ from .runtime import (
4
+ CompletionError,
5
+ EventEnvelope,
6
+ EventLog,
7
+ IntegrityError,
8
+ IdempotencyConflict,
9
+ AuthorizationError,
10
+ InvalidTransition,
11
+ KernelError,
12
+ VALID_STATES,
13
+ reduce_events,
14
+ )
15
+ from .contracts import Artifact, Authorization, Contract, Effect, Requirement, Subject, RUNTIME_PROTOCOL_VERSION
16
+ from .contract_packs import (
17
+ COMPONENT_RESULT_PROTOCOL,
18
+ CONTRACT_EXECUTION_PROTOCOL,
19
+ ComponentHandoff,
20
+ ComponentResult,
21
+ ContractBindingError,
22
+ ContractComponent,
23
+ ContractExecutionError,
24
+ ContractExecutionReport,
25
+ ContractPack,
26
+ ContractPackExecutor,
27
+ )
28
+ from .verifiers import (
29
+ Evidence,
30
+ CombinedVerifierRegistry,
31
+ FilesystemVerifierRegistry,
32
+ GateDecision,
33
+ PackRegistry,
34
+ VerifierPack,
35
+ VerifierRunner,
36
+ VerifierSpec,
37
+ VerifierDefinition,
38
+ VerifierRegistry,
39
+ VerificationRequest,
40
+ VerificationResult,
41
+ VerifierContractAdapter,
42
+ VerifierContractExecution,
43
+ apply_gate,
44
+ builtin_verifier_root,
45
+ normalize_result,
46
+ snapshot_evidence,
47
+ summarize_metrics,
48
+ normalize_requirements,
49
+ )
50
+ from .builtins import CITATION_TRUTH_FIT_SPEC, FILE_SPEC, build_builtin_registry, collect_markdown
51
+ from .verifier_ids import validate_verifier_id
52
+ from .state_ids import validate_state_id
53
+ from .states import CombinedStateRegistry, FilesystemStateRegistry, META_STATE_PROTOCOL_VERSION, SKILL_STATE_DECLARATION_VERSION, SkillStateDeclaration, StateContractAdapter, StateDefinition, StateDefinitionError, StateExecutionError, StateExecutionResult, StateTransitionError, StateMachine, build_builtin_state_registry, build_state_registry, check_state_invariants, execute_state
54
+ from .workspace import META_STATE_SNAPSHOT_VERSION, TaskWorkspace, WorkspaceError, WorkspacePaths, WORKSPACE_KINDS, WORKSPACE_PROTOCOL_VERSION, state_snapshot_hash, workspace_path
55
+
56
+ __all__ = [
57
+ "CompletionError",
58
+ "EventEnvelope",
59
+ "EventLog",
60
+ "IntegrityError",
61
+ "IdempotencyConflict",
62
+ "AuthorizationError",
63
+ "InvalidTransition",
64
+ "KernelError",
65
+ "VALID_STATES",
66
+ "reduce_events",
67
+ "Artifact",
68
+ "Authorization",
69
+ "Contract",
70
+ "Effect",
71
+ "Requirement",
72
+ "Subject",
73
+ "RUNTIME_PROTOCOL_VERSION",
74
+ "COMPONENT_RESULT_PROTOCOL",
75
+ "CONTRACT_EXECUTION_PROTOCOL",
76
+ "ComponentHandoff",
77
+ "ComponentResult",
78
+ "ContractBindingError",
79
+ "ContractComponent",
80
+ "ContractExecutionError",
81
+ "ContractExecutionReport",
82
+ "ContractPack",
83
+ "ContractPackExecutor",
84
+ "Evidence",
85
+ "CombinedVerifierRegistry",
86
+ "FilesystemVerifierRegistry",
87
+ "VerifierRegistry",
88
+ "GateDecision",
89
+ "PackRegistry",
90
+ "VerifierPack",
91
+ "VerifierRunner",
92
+ "VerifierSpec",
93
+ "VerifierDefinition",
94
+ "VerificationRequest",
95
+ "VerificationResult",
96
+ "VerifierContractAdapter",
97
+ "VerifierContractExecution",
98
+ "apply_gate",
99
+ "builtin_verifier_root",
100
+ "normalize_result",
101
+ "snapshot_evidence",
102
+ "summarize_metrics",
103
+ "normalize_requirements",
104
+ "CITATION_TRUTH_FIT_SPEC",
105
+ "FILE_SPEC",
106
+ "build_builtin_registry",
107
+ "collect_markdown",
108
+ "validate_verifier_id",
109
+ "validate_state_id",
110
+ "FilesystemStateRegistry",
111
+ "CombinedStateRegistry",
112
+ "META_STATE_PROTOCOL_VERSION",
113
+ "SKILL_STATE_DECLARATION_VERSION",
114
+ "SkillStateDeclaration",
115
+ "StateDefinition",
116
+ "StateContractAdapter",
117
+ "StateDefinitionError",
118
+ "StateExecutionError",
119
+ "StateExecutionResult",
120
+ "StateTransitionError",
121
+ "StateMachine",
122
+ "build_builtin_state_registry",
123
+ "build_state_registry",
124
+ "execute_state",
125
+ "check_state_invariants",
126
+ "TaskWorkspace",
127
+ "WorkspaceError",
128
+ "WorkspacePaths",
129
+ "WORKSPACE_KINDS",
130
+ "WORKSPACE_PROTOCOL_VERSION",
131
+ "META_STATE_SNAPSHOT_VERSION",
132
+ "workspace_path",
133
+ "state_snapshot_hash",
134
+ ]
135
+
136
+ def _distribution_version() -> str:
137
+ """Resolve the runtime version from the source metadata or installed wheel."""
138
+ from pathlib import Path
139
+ import importlib.metadata
140
+ import re
141
+
142
+ # During source-tree execution package metadata may describe an older
143
+ # globally installed wheel. Prefer the adjacent pyproject as the source
144
+ # of truth in that case.
145
+ pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml"
146
+ try:
147
+ text = pyproject.read_text(encoding="utf-8")
148
+ match = re.search(r"(?m)^version\s*=\s*['\"]([^'\"]+)['\"]", text)
149
+ if match:
150
+ return match.group(1)
151
+ except OSError:
152
+ pass
153
+ try:
154
+ return importlib.metadata.version("bensz-skill-kernel")
155
+ except importlib.metadata.PackageNotFoundError:
156
+ return "0.0.0"
157
+
158
+
159
+ __version__ = _distribution_version()
@@ -0,0 +1,93 @@
1
+ """Implementations for the small, domain-neutral atomic verifier packs.
2
+
3
+ This module lives beside the directory contracts so reviewers can inspect the
4
+ rules independently from the registry and runner plumbing. Each directory's
5
+ ``scripts/verify.py`` is a thin JSON-stdio adapter around ``run_atomic``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ from pathlib import Path
13
+ from typing import Any, Mapping
14
+
15
+ from bensz_skill_kernel.runtime import ALLOWED_TRANSITIONS, EventLog
16
+
17
+
18
+ def _parts(request: Any) -> tuple[Mapping[str, Any], Mapping[str, Any]]:
19
+ if hasattr(request, "subject"):
20
+ return request.subject, request.context
21
+ return request.get("subject") or {}, request.get("context") or {}
22
+
23
+
24
+ def _evidence_map(request: Any, evidence: Mapping[str, Any] | None) -> Mapping[str, Any]:
25
+ if evidence:
26
+ return evidence
27
+ if hasattr(request, "evidence"):
28
+ return {item.ref: item for item in request.evidence}
29
+ raw = request.get("evidence") or []
30
+ return {str(item.get("ref", index)): item for index, item in enumerate(raw) if isinstance(item, Mapping)}
31
+
32
+
33
+ def run_atomic(name: str, request: Any, evidence: Mapping[str, Any] | None = None) -> Mapping[str, Any]:
34
+ subject, context = _parts(request)
35
+ evidence = _evidence_map(request, evidence)
36
+ if name == "contract-conformance":
37
+ required = tuple(context.get("required_fields", ()))
38
+ missing = [field for field in required if field not in subject]
39
+ return _result(not missing, {"required_fields": list(required), "missing": missing}, "missing-field", missing)
40
+ if name == "path-scope":
41
+ raw_paths = subject.get("paths") or ([subject["path"]] if subject.get("path") else [])
42
+ allowed = [Path(item).expanduser().resolve() for item in context.get("allowed_paths", ())]
43
+ violations = []
44
+ for raw in raw_paths:
45
+ target = Path(raw).expanduser().resolve()
46
+ # Scope is lexical after ``resolve()`` and must not depend on an
47
+ # allowed directory already existing. Requiring ``is_dir()``
48
+ # made valid output paths fail before their parent was created.
49
+ if not any(target == root or root in target.parents for root in allowed):
50
+ violations.append(str(target))
51
+ return _result(not violations, {"paths": list(raw_paths), "violations": violations}, "path-out-of-scope", violations)
52
+ if name == "schema-conformance":
53
+ data = subject.get("data", subject)
54
+ schema = context.get("schema", {})
55
+ required = schema.get("required", ()) if isinstance(schema, Mapping) else ()
56
+ missing = [key for key in required if not isinstance(data, Mapping) or key not in data]
57
+ return _result(not missing, {"missing": missing}, "schema-required-field", missing)
58
+ if name == "diff-scope":
59
+ changed = set(subject.get("changed_paths", ()))
60
+ violations = sorted(changed - set(context.get("allowed_paths", ())))
61
+ return _result(not violations, {"changed": sorted(changed), "violations": violations}, "unexpected-change", violations)
62
+ if name == "secret-redaction":
63
+ raw = json.dumps(subject, ensure_ascii=False, default=str)
64
+ patterns = (r"[\"']?(?:api[_-]?key|token|password|cookie)[\"']?\s*[:=]\s*[\"']?[^,\s}\"']+", r"sk-[A-Za-z0-9_-]{8,}")
65
+ matches = [pattern for pattern in patterns if re.search(pattern, raw, re.IGNORECASE)]
66
+ return {"verdict": "fail" if matches else "pass", "facts": {"matched_patterns": len(matches)}, "findings": [{"id": "secret-detected", "verdict": "fail"}] if matches else []}
67
+ if name == "evidence-provenance":
68
+ def value(item: Any, key: str) -> Any:
69
+ return item.get(key) if isinstance(item, Mapping) else getattr(item, key, None)
70
+ invalid = [ref for ref, item in evidence.items() if not value(item, "source_type") or not value(item, "content_hash") or not value(item, "collected_at")]
71
+ return _result(not invalid, {"evidence_count": len(evidence)}, "missing-provenance", invalid)
72
+ if name == "event-integrity":
73
+ path = subject.get("path")
74
+ if not path:
75
+ return _result(False, {}, "missing-events-path", ["events"])
76
+ try:
77
+ count = len(EventLog(path).read())
78
+ except Exception as exc:
79
+ return {"verdict": "fail", "uncertainty_reason": str(exc), "findings": [{"id": "event-integrity", "verdict": "fail"}]}
80
+ return {"verdict": "pass", "facts": {"event_count": count}, "findings": []}
81
+ if name == "state-transition":
82
+ current, target = subject.get("current_state"), subject.get("target_state")
83
+ allowed = target in ALLOWED_TRANSITIONS.get(current, ())
84
+ return _result(allowed, {"current_state": current, "target_state": target}, "illegal-transition", [target] if not allowed else [])
85
+ if name == "task-completeness":
86
+ required = tuple(context.get("required_fields", ("artifacts", "verifications", "delivery_report")))
87
+ missing = [key for key in required if not subject.get(key)]
88
+ return _result(not missing, {"missing": missing}, "task-incomplete", missing)
89
+ raise ValueError(f"unknown atomic verifier: {name}")
90
+
91
+
92
+ def _result(ok: bool, facts: Mapping[str, Any], finding_id: str, values: list[Any]) -> Mapping[str, Any]:
93
+ return {"verdict": "pass" if ok else "fail", "facts": dict(facts), "findings": [{"id": finding_id, "value": value, "verdict": "fail"} for value in values]}