Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion doc/architecture/SPEC/engine/steps/knowledge.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# engine/steps/knowledge.py —— 规范

<!-- verified-against: 2026-10-04 -->
<!-- verified-against: 2026-10-06 -->

`知识服务的 knowledge.* 步骤 · refactor-status: new`

Expand Down Expand Up @@ -34,3 +34,22 @@ PR,且只在 `ALLOW_PUSH=1` 与 `ALLOW_POST=1` 同时成立、非 dry run、
累计预算与草稿。`from_existing` 允许 knowledge/knowledge-deepen 从已合并 KB 开始。
`unlimited_subscription` 显式传入两角色订阅无限模式;预算和语义完成门槛由同一 stage owner 处理。
`kb widen` / `kb deepen` 经同一 playbook/step 指定这两个知识阶段,旧规则 deepen 保持原契约。

## 2026-10-06 显式基础知识部分发布

`foundation_mode` 缺省 `strict`,未显式选择时不添加部分发布参数。
`partial` 只允许订阅无限的 `knowledge`,或带原始 `foundation_record` 的
`knowledge-deepen`;CLI 的 `--foundation-record` 经步骤转换为 `Path`,以
`foundation_record_path` 传给 `run_stage`。不兼容的阶段、缺失记录和非法模式由
stage 拒绝,步骤返回 BLOCKED,不能以参数缺失自动降级为部分发布。

`knowledge` 的显式部分发布只重放原批次的真实任务和独立评审,不追加模型调用,
不重置修正次数,也不改变生成身份。新深度批次将模式及原发布记录/收据哈希纳入
身份;七维认可门槛和完整目录分母保持不变。`published` 只表示 PR 已发布,
仍须单独保留 `init_complete=false`、六维基础知识缺口与结构覆盖结果。
原始 `blocked`/`partial` 状态仍返回 BLOCKED;不能把这些状态当作已完成。

证明与发布收据契约由 [`foundation_publication`](../../kb_service/foundation_publication.md)
负责,使用入口见[部分发布说明](../../../../knowledge/partial-foundation.md)。
接口测试:`test_kb_foundation_partial_interfaces.py`;原生证明和门槛测试:
`test_kb_foundation_explicit_partial.py`。
10 changes: 9 additions & 1 deletion doc/architecture/SPEC/kb_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ proof `basis` 缺省 supported,旧区块不改写。verified_absent 当前只
可选政策 `semantic_depth: {per_facet_gt: 0.90}` 对每个维度严格执行大于目标,并要求
每个功能至少一个认可区块;79 功能时每维至少 72,553 分母不缩减。target_met 与
批次遍历完成分别记录。提取前复核结构覆盖;结构门槛不足先阻断,不消耗模型调用。
结构覆盖与深度门槛均须满足才能发布。深度未达标 dry run 为 partial,保存预览但步骤不成功;发布阻断。
默认 strict 流程中,结构覆盖与深度门槛均须满足才能发布。深度未达标 dry run 为 partial,保存预览但步骤不成功;发布阻断。
执行 API 的 feature_ids 只限定调度范围;审计仍包含完整政策中的所有功能和维度。
显式列表保留执行顺序并纳入输入摘要;零深度功能仍优先,之后按列表顺序开展首轮。
并行批次各自保留检查点,合并认可页面后须重新通过全量验收,不能发布分组的部分结果。
Expand All @@ -618,6 +618,14 @@ modules/knowledge 的公共阶段预算仅在显式启用时改为 None;单独
仍保留原记账上限。该模式绑定检查点身份,无此选项时的旧摘要保持不变;
feature-discovery 与 knowledge-deepen 继续使用各自已有的检查点及身份实现。

`--foundation-mode partial` 是显式基础知识部分发布及其新深度批次交接选项,
不降低七维认可门槛。基础发布只重放已经结束的订阅批次,保留原生成身份、任务、
修正次数、真实未知项与 `init_complete=false`;源码、原生证明、格式和结构覆盖
仍须通过。新深度批次要求原始已合并发布记录,绑定模式、记录和收据哈希,
同一完整功能目录和七维分母不变。收据、紧凑 PR 正文和 prepared 恢复契约见
[`foundation_publication`](kb_service/foundation_publication.md),操作入口见
[部分发布说明](../../knowledge/partial-foundation.md)。

### 2026-10-02 轻量认可、13 功能任务与轨迹留存

政策可指定 `semantic_depth.acceptance_mode: lightweight`,CLI 可显式选择
Expand Down
42 changes: 42 additions & 0 deletions doc/architecture/SPEC/kb_service/foundation_publication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# kb_service/foundation_publication.py —— 规范

<!-- verified-against: 2026-10-06 -->

## 职责

为显式 `--foundation-mode partial` 保存和复核基础知识发布收据,并为新深度批次
验证原发布记录。调用方的默认完整门槛保持不变;使用方式见
[部分发布说明](../../../knowledge/partial-foundation.md)。

## 不变量

- 只发布已经完成初轮扫描、保留真实原生任务的固定源码批次。已有基线未覆盖的
功能和 owner 缺少初轮任务时拒绝发布;已处理范围不能称为已补齐全部知识。
- 生产文件结构覆盖达到原政策目标;源码、目录与政策身份、引用、格式及原生
认可仍须有效。不能修改功能集合、分母、语义认可条件或失败记录来满足门槛。
- `freeze_receipt` 重放每个任务的原生证明,再冻结生成身份、KB/source SHA、
全部终态任务的输入/结果摘要、实际进入页面的认可区块、输出文件哈希及未知项。
收据按内容哈希在原始 Git 外阶段目录独占创建;既有字节或路径损坏必须拒绝。
- `verify_prepared_receipt` 核对原始 partial 模式、收据范围和 prepared 文件,
从固定源码及实际准备发布的字节重新计算覆盖。不能只信检查点里的计数。
旧 strict prepared 发布保持原行为,不能偷偷转换为 partial。
- `foundation_handoff` 只接受原始 published、非 dry-run 的 partial 记录。
记录、收据、任务、prepared 文件、当前合并基线及政策必须一致,且真实 PR
状态为 MERGED。返回原记录/收据路径与哈希、固定 pin、目录绑定和输出文件哈希。
下游以这些字节建立新批次身份,发生变化即拒绝复用。
- 发布状态只表示 PR 传输。收据保留 `init_complete=false`、原六维目标结果和
所有未知项;结构覆盖、六维基础知识、七维深度认可及实际测试执行分别表述。

## PR 正文与恢复

`render_partial_body` 只渲染汇总数量、固定身份、真实未知维度和收据引用;完整
模型输入、输出、评审及历史仍在 Git 外。费用未报告时为未知。
若原 prepared 发布因正文过长而未成功,官方恢复路径须先通过全部原生、源码和
收据校验,再仅刷新正文元数据:commit、文件字节、任务和修正次数不得改变,
不得重新生成知识或伪造发布状态。

## 验证

`test_kb_foundation_explicit_partial.py` 覆盖原生证明、范围、收据、恢复与门槛;
`test_kb_foundation_partial_interfaces.py` 覆盖 CLI、campaign、worker 与 assembly
身份传递;`test_kb_foundation_partial_prbody.py` 覆盖紧凑正文和官方正文恢复。
34 changes: 34 additions & 0 deletions doc/knowledge/partial-foundation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Explicit partial foundation publication

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Index the partial-foundation documentation

This new page is not linked from doc/knowledge/README.md or another documentation index—repository-wide search for partial-foundation finds only this page—so readers entering through the knowledge documentation cannot discover it. Add the page to the directory's existing documentation index in the same change.

AGENTS.md reference: AGENTS.md:L7-L11

Useful? React with 👍 / 👎.


The default initialization workflow requires all six foundation facets for every
catalog feature. `--foundation-mode partial` is an explicit alternative for an
existing, source-pinned subscription batch whose genuine native tasks have
already finished. It reconstructs approved sections and inspects the declared
scope without calling either model again. Unknown sections remain unknown;
local publication exclusions retain their original native evidence archives.

Publication still requires the configured production-file threshold, valid
catalog and source inventories, genuine native proofs, valid knowledge files,
and a complete terminal scope. Missing initial feature tasks block publication
unless that feature was already covered in the merged baseline. The external
immutable receipt records the original generation identity, source/catalog and
policy, terminal tasks, active approvals, exact output hashes and unknowns.
Prepared publication retries recheck these bindings and the actual pinned
coverage before writing. Publication status describes PR transport: the
foundation target remains false when six-facet gaps remain, and
`init_complete` remains false.

After this foundation PR merges, start a **new depth batch** with
`--foundation-mode partial --foundation-record /path/to/original/knowledge.json`.
The original published record and receipt, merged output bytes and fixed source
pin become immutable depth inputs. The campaign and standalone depth audit use
the same structural prerequisite; six-facet foundation completion is reported
separately. The audit also requires native approval reports in this mode.

This option preserves the catalog and all semantic acceptance criteria. Each
feature must have recognized depth knowledge, each of the seven depth facets
must exceed the policy threshold, and production coverage must meet the policy
target. The denominator remains the complete frozen catalog times seven.
Partition workers report their actual execution scope separately from global
semantic completion; completing a partition never implies the whole catalog
has passed. Retrieval, independent review and CI remain delivery gates.
37 changes: 35 additions & 2 deletions eval/knowledge-depth/assemble_depth_campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ def _filtered(text, page, retired):
return text


def assemble(root: Path, state: Path, source: Path, *, retirement_reports=()):
def assemble(root: Path, state: Path, source: Path, *, retirement_reports=(),
foundation_mode="strict", foundation_record_path: Path | None = None):
"""Return metadata and validated writes without modifying any input."""
root, state, source = root.resolve(), state.resolve(), source.resolve()
metadata_raw = (state / "campaign.json").read_bytes()
Expand All @@ -181,6 +182,25 @@ def assemble(root: Path, state: Path, source: Path, *, retirement_reports=()):
if not isinstance(repo, str) or not re.fullmatch(r"[a-zA-Z0-9_-]+", repo) \
or not all(isinstance(sha, str) and re.fullmatch(r"[0-9a-f]{40}", sha) for sha in (baseline, pin)):
raise ValueError("campaign needs a safe repo and immutable source/knowledge SHAs")
if foundation_mode not in ("strict", "partial") or (
foundation_mode == "partial") != bool(foundation_record_path):
raise ValueError("partial assembly requires its foundation record; strict assembly cannot use one")
if campaign.get("foundation_mode", "strict") != foundation_mode:
raise ValueError("assembly foundation mode differs from campaign")
foundation = None
if foundation_mode == "partial":
from infermatrix_copilot.kb_service.foundation_publication import foundation_handoff
from infermatrix_copilot.kb_service.init_support import InitError
from infermatrix_copilot.kb_service.sources import KnowledgeRepo
try:
foundation = foundation_handoff(foundation_record_path, knowledge=KnowledgeRepo(root),
baseline=baseline, repo=repo, pin=pin)
except InitError as exc:
raise ValueError(str(exc)) from exc
if campaign.get("foundation") != foundation:
raise ValueError("assembly foundation record differs from campaign")
elif campaign.get("foundation") is not None:
raise ValueError("strict assembly cannot reuse partial foundation metadata")
if _git(root, "rev-parse", baseline + "^{commit}").decode().strip() != baseline \
or _git(source, "rev-parse", "HEAD").decode().strip() != pin \
or _git(source, "status", "--porcelain", "--untracked-files=no").strip():
Expand Down Expand Up @@ -236,6 +256,12 @@ def assemble(root: Path, state: Path, source: Path, *, retirement_reports=()):
(("stage", "knowledge-deepen"), ("repo", repo), ("pin", pin), ("kb_base_sha", baseline))):
raise ValueError("worker checkpoint identity differs from campaign: " + worker)
depth = record.get("depth", {})
if foundation is not None and (depth.get("foundation_mode") != "partial"
or depth.get("foundation") != foundation):
raise ValueError("worker foundation identity differs from campaign: " + worker)
if foundation is None and (depth.get("foundation_mode", "strict") != "strict"
or depth.get("foundation") is not None):
raise ValueError("strict campaign cannot reuse partial worker state: " + worker)
entries, accepted = depth.get("features", {}), depth.get("accepted", {})
if not isinstance(entries, dict) or not isinstance(accepted, dict) or set(entries) - set(features):
raise ValueError("worker checkpoint has invalid feature state: " + worker)
Expand Down Expand Up @@ -267,6 +293,8 @@ def assemble(root: Path, state: Path, source: Path, *, retirement_reports=()):
eligible_features.add(feature.id)
old = baseline_pages.get(page)
related = f"[功能概览]({PurePosixPath(feature.page).name}) · [owner 入口](_index.md)"
if foundation_mode == "partial" and _baseline(root, baseline, "knowledge/" + feature.page) is None:
related = "[owner 入口](_index.md)"
expected_body = _outside_blocks(old) if old is not None else ["# " + _one_line(feature.title) + ":实现深读", related]
if _outside_blocks(candidate) != expected_body:
raise ValueError("accepted page alters prose outside approved blocks: " + feature.id)
Expand Down Expand Up @@ -342,6 +370,8 @@ def assemble(root: Path, state: Path, source: Path, *, retirement_reports=()):
"eligible_feature_count": len(eligible_features),
"pages": changes, "index_links": new_links, "planned_files": sorted(writes),
"receipts": receipts, "native_archive_audit_required": True, "problems": []}
if foundation is not None:
report.update(foundation_mode="partial", foundation=foundation)
return report, writes


Expand All @@ -351,6 +381,8 @@ def main():
parser.add_argument("--state", type=Path, required=True)
parser.add_argument("--source-tree", type=Path, required=True)
parser.add_argument("--retirement-report", type=Path, action="append", default=[])
parser.add_argument("--foundation-mode", choices=("strict", "partial"), default="strict")
parser.add_argument("--foundation-record", type=Path)
parser.add_argument("--report", type=Path)
parser.add_argument("--apply", action="store_true")
args = parser.parse_args()
Expand All @@ -364,7 +396,8 @@ def main():
for n in range(campaign["workers"]))]:
handle = stack.enter_context(lock.open("r"))
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
report, writes = assemble(args.root, args.state, args.source_tree, retirement_reports=args.retirement_report)
report, writes = assemble(args.root, args.state, args.source_tree, retirement_reports=args.retirement_report,
foundation_mode=args.foundation_mode, foundation_record_path=args.foundation_record)
report["applied"] = args.apply
if args.apply:
for page, text in writes.items():
Expand Down
Loading
Loading