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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Fixed

- Distinguish an absent repository-policy entry from dangling/cyclic or unreadable configuration sources. Require the resolved config to be a regular file inside its selected root before reading; retain internal file links and explicit root aliases, validate both comparison trees and sanitize source I/O errors. Invalid sources no longer fall back to compatibility defaults or read external policy contents. Document deliberate migration for external links and Git worktree targets, with explicit non-atomic/hard-link/mount limits. Valid-policy scoring/defaults, published v0.24.0 artifacts, Action refs and frozen research are unchanged.

- Preserve damaged local history instead of treating malformed/unreadable indexes as empty and overwriting them on append. Reject non-object rows, duplicate decoded names, non-finite numbers, invalid scores and known linked/reparse/nonregular store paths; retain partial legacy snapshots and unknown scores. The Python append helper serializes before writing and flushes/syncs a same-directory temporary file before replacement, with tested write/sync/replacement-failure preservation. CLI history failures use exit `2` without success output; full-history validation precedes visibility/retention limits. No automatic repair, writer locking, power-loss durability or evidence-authenticity claim is implied. Rule scores, CI artifacts, published v0.24.0 artifacts and Action refs are unchanged.

- Reject duplicate decoded JSON member names throughout repository configuration instead of silently adopting the last policy/suppression value, including escaped aliases and equal-valued repeats. Reject non-standard numeric constants and Boolean format versions while preserving valid-policy defaults and numeric `1.0` compatibility. Doctor derives policy/schema metadata from one read. Invalid baseline and candidate configs remain errors despite CLI threshold overrides; rejection does not overwrite reports or certify policy adequacy. Published v0.24.0 artifacts, Action refs, rule scores and frozen research are unchanged.
Expand Down
31 changes: 31 additions & 0 deletions docs/doctor.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,37 @@ sandbox, authenticity check or proof that the policy is adequate. The direct
`parse_policy(dict)` API cannot recover duplicate names that a caller has already
discarded while decoding. Published v0.24.0 packages and Action refs are unchanged.

## Configuration file boundary (unreleased)

Source builds distinguish an absent `.agentconfigscore.json` directory entry
from an existing but invalid source. Only a genuinely absent config uses
compatibility defaults. Dangling/cyclic links, failed metadata access, external
targets and non-regular sources are configuration errors, not missing config.
The source must resolve to a regular file inside the selected repository root;
internal file links are supported, and an explicitly selected root alias defines
the resolved boundary. The resolved target is opened as strict UTF-8. Raw target
paths and filesystem error details are not echoed in these source diagnostics.

The same loader serves scans, feedback, both sides of compare/diff and doctor.
Failure keeps operational exit `2` and no success report for policy-using CLI
commands; doctor emits a `config` error with exit `1`. Threshold overrides cannot
authorize an invalid source. No source/report is repaired or rewritten by the
check. Unscored `guardrails` and workflow-only initialization remain separate
interfaces and do not certify repository policy.

For a shared config, prefer a relative link to a tracked in-repository JSON file
so the target exists inside each Git baseline worktree. A link back to the
original checkout can be external to the temporary baseline root and is rejected.
For an invalid link, review/migrate the intended policy into the repository or
repair the link deliberately, then rerun doctor. Do not delete the link merely
to make the tool fall back to defaults. Git/platform link support still matters.

This changes compatibility for external config links and is not yet in v0.24.0.
It is not an atomic snapshot or filesystem sandbox: concurrent replacement of
parents/targets remains possible between metadata checks and open. Hard-link
identity and mount boundaries are not certified; workflow/report readers are
separate interfaces. Keep the tree stable during validation and comparison.

## Instruction and repository access

The `repository-paths` check reports an error if index traversal fails, for
Expand Down
9 changes: 9 additions & 0 deletions docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ v0.24.0. See [failure and compatibility details](doctor.md#configuration-decodin
The check does not validate policy adequacy, authenticate config authors, provide
an atomic snapshot or recover names already lost by a caller's JSON decoder.

Unreleased config-source checks also distinguish absent files from dangling
links. Config must resolve to a regular file inside the selected repository root;
internal file links and explicit root aliases remain supported. External/cyclic/
nonregular/unreadable sources fail rather than using defaults. Both Git worktrees
must satisfy the boundary independently. This is not atomic against concurrent
path replacement, and hard-link/mount identity is not certified. Workflow-only
setup and unscored guardrails remain separate from policy validation. See
[compatibility and migration](doctor.md#configuration-file-boundary-unreleased).

## Local history integrity

Unreleased source builds distinguish an absent local history index from damaged
Expand Down
2 changes: 2 additions & 0 deletions docs/simplified/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ v0.24.0+ 提供[工具源码身份(英文)](../report-identity.md),用于

尚未发布的源码拒绝各层配置中解码后重复的 JSON 字段名、非标准 `NaN`/`Infinity` 常量以及布尔类型的格式 `version`,不选择重复字段的第一个或最后一个值。比较的两侧都必须通过验证;命令行门槛覆盖不能批准格式错误的配置。`doctor` 从同一次读取的内容获取策略和 schema 元数据。这些变化不在 v0.24.0 中。[失败行为与兼容性细节(英文)](../doctor.md#configuration-decoding)。这不证明策略充分、不认证作者、不提供原子快照,也不能恢复调用方 JSON 解析时已经丢失的重复字段。

尚未发布的配置来源检查还区分文件不存在与链接失效,要求配置解析为选定仓库根目录内的普通文件。仓库内的文件链接和显式根目录别名仍受支持;越界、循环、非普通文件或不可读来源报错,不回退到默认值。Git 比较两侧各自满足边界;这不对并发路径替换提供原子保证,也不认证硬链接/挂载身份。只生成工作流的初始化及非计分 guardrails 不验证策略。[兼容性与迁移(英文)](../doctor.md#configuration-file-boundary-unreleased)。这些变化不在 v0.24.0 中。

## 本地历史完整性

尚未发布的源码区分本地历史索引不存在与数据损坏/不可读。历史无效时返回操作错误 `2`,不是空的成功报告。Python 追加接口拒绝重置损坏的索引,并在同一目录写入完整临时文件后替换,不直接截断原记录。旧版部分字段快照仍受支持;缺失分数表示未知。带链接或 reparse 的历史路径需要手动迁移。这些变化不在 v0.24.0 中。
Expand Down
2 changes: 2 additions & 0 deletions docs/simplified/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ agent-config-score doctor

尚未发布的源码拒绝配置中重复的字段名(包括嵌套对象和转义拼写)、`NaN`/`Infinity` 常量及布尔类型的格式 `version`,不会悄悄采用覆盖后的值。请审核并为每个字段保留一个值。扫描、`feedback`、`diff` 和 `compare` 返回 `2` 且不生成成功报告;doctor 给出 `config` 错误并返回 `1`。命令行门槛覆盖不能跳过配置验证;缺失配置时的默认行为不变。这些变化不在 v0.24.0 中。[配置解析细节(英文)](../doctor.md#configuration-decoding)。

尚未发布的配置来源检查还要求 `.agentconfigscore.json` 解析为选定仓库根目录内的普通文件。只有目录项不存在时才使用默认值;失效、越界、循环或不可读的来源均报错。仓库内的文件链接及用户显式指定的根目录别名仍受支持。共享配置宜使用指向已跟踪文件的相对链接,确保每个 Git 基线都有自己的仓库内目标。检查期间保持目录稳定;这不是原子文件系统沙箱。无效链接须明确修复,不要删除链接来绕过策略。[配置边界与迁移(英文)](../doctor.md#configuration-file-boundary-unreleased)。已发布的 v0.24.0 及 Action 引用不变。

若没有找到支持的指令文件,请编写符合仓库实际情况的指令,或采用[模板(英文)](../../examples/README.md)。不要仅为涨分添加词句。[完整发现范围(英文)](../../README.md#what-it-scans)。

## 3. 检查当前目录
Expand Down
2 changes: 2 additions & 0 deletions docs/traditional/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ v0.24.0+ 提供[工具原始碼身分(英文)](../report-identity.md),用

尚未發布的原始碼拒絕各層設定中解碼後重複的 JSON 欄位名稱、非標準 `NaN`/`Infinity` 常數以及布林型別的格式 `version`,不選取重複欄位的第一個或最後一個值。比較的兩側都必須通過驗證;命令列門檻覆寫不能核准格式錯誤的設定。`doctor` 從同一次讀取的內容取得策略及 schema 中繼資料。這些變更不在 v0.24.0 中。[失敗行為與相容性細節(英文)](../doctor.md#configuration-decoding)。這不證明策略充分、不認證作者、不提供原子快照,也不能復原呼叫端 JSON 解析時已遺失的重複欄位。

尚未發布的設定來源檢查也區分檔案不存在與連結失效,要求設定解析為選定儲存庫根目錄內的一般檔案。儲存庫內的檔案連結與明確根目錄別名仍受支援;越界、循環、非一般檔案或無法讀取的來源報錯,不退回預設值。Git 比較兩側各自滿足邊界;這不對並行路徑替換提供原子保證,也不認證硬連結/掛載身分。僅產生工作流程的初始化與不計分 guardrails 不驗證策略。[相容性與遷移(英文)](../doctor.md#configuration-file-boundary-unreleased)。這些變更不在 v0.24.0 中。

## 本機歷史完整性

尚未發布的原始碼區分本機歷史索引不存在與資料損壞/無法讀取。歷史無效時返回操作錯誤 `2`,不是空的成功報告。Python 附加介面拒絕重設損壞的索引,並在同一目錄寫入完整暫存檔案後替換,不直接截斷原記錄。舊版部分欄位快照仍受支援;缺失分數表示未知。含連結或 reparse 的歷史路徑需要手動遷移。這些變更不在 v0.24.0 中。
Expand Down
2 changes: 2 additions & 0 deletions docs/traditional/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ agent-config-score doctor

尚未發布的原始碼拒絕設定中重複的欄位名稱(包括巢狀物件及跳脫拼寫)、`NaN`/`Infinity` 常數與布林型別的格式 `version`,不會悄悄採用覆蓋後的值。請審閱並為每個欄位保留一個值。掃描、`feedback`、`diff` 與 `compare` 返回 `2` 且不產生成功報告;doctor 提供 `config` 錯誤並返回 `1`。命令列門檻覆寫不能略過設定驗證;缺少設定時的預設行為不變。這些變更不在 v0.24.0 中。[設定解析細節(英文)](../doctor.md#configuration-decoding)。

尚未發布的設定來源檢查也要求 `.agentconfigscore.json` 解析為選定儲存庫根目錄內的一般檔案。只有目錄項目不存在時才使用預設值;失效、越界、循環或無法讀取的來源均報錯。儲存庫內的檔案連結與使用者明確指定的根目錄別名仍受支援。共享設定宜使用指向已追蹤檔案的相對連結,確保每個 Git 基準都有自己的儲存庫內目標。檢查期間保持目錄穩定;這不是原子檔案系統沙箱。無效連結須明確修復,不要刪除連結來繞過策略。[設定邊界與遷移(英文)](../doctor.md#configuration-file-boundary-unreleased)。已發布的 v0.24.0 與 Action 參照不變。

若沒有找到支援的指令檔,請撰寫符合儲存庫實際需求的指令,或使用[範本(英文)](../../examples/README.md)。不要只為提高分數添加詞句。[完整探索範圍(英文)](../../README.md#what-it-scans)。

## 3. 檢查目前目錄
Expand Down
9 changes: 9 additions & 0 deletions docs/translations.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ history is not automatically appended by scans or imported from CI artifacts.
This is unreleased source behavior; executable guide examples, rule scores,
published v0.24.0 artifacts, Action refs and frozen research remain unchanged.

Also on 2026-10-28, built on `7d19d47c0818edecaf0c93a50190315e069a6ab0`, both
core guides/limitations were synchronized for repository-policy source checks:
absent entries versus invalid links, in-root regular-file targets, retained
internal/root aliases, both baseline/candidate boundaries and sanitized source
I/O failures. Migration, platform/link and non-atomic/hard-link/mount limits are
explicit; workflow-only setup and unscored guardrails remain separate. This is
unreleased behavior, not a v0.24.0, Action-ref, scoring or frozen-evidence update.
Executable guide examples are unchanged.

When an English source page changes, review both variants together and record
the new source revision here. Preserve executable examples, rule IDs and JSON
keys verbatim. Keep the score limitations and failure/exit semantics aligned;
Expand Down
10 changes: 10 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ report; doctor returns `1` with a `config` error. Threshold overrides do not byp
config validation. Missing-config defaults remain unchanged. This is not in
v0.24.0; see [configuration decoding](doctor.md#configuration-decoding).

Unreleased config-source checks also require `.agentconfigscore.json` to resolve
to a regular file inside the selected repository root. Only an absent entry uses
defaults; dangling/external/cyclic/unreadable sources are errors. Internal file
links and explicit repository-root aliases remain supported. Use a relative,
tracked shared config so each Git baseline has its own in-root target. Keep the
tree stable: this is not an atomic filesystem sandbox. Existing invalid links
need deliberate repair, not deletion to bypass policy. See
[config boundary and migration](doctor.md#configuration-file-boundary-unreleased).
Published v0.24.0 and Action refs are unchanged.

If doctor finds no supported instruction files, add repository-specific
instructions or adapt one of the [templates](../examples/README.md). Do not add
phrases just to increase the score. See [supported discovery](../README.md#what-it-scans).
Expand Down
40 changes: 40 additions & 0 deletions scripts/verify_installed_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,46 @@ def verify(python: Path, expected_version: str) -> list[str]:
config.write_bytes(original[0])
checks.append("strict policy JSON across aliases, module, doctor, diff, compare and feedback without success reports or overwrites")

policy_scope = workspace / "policy source boundary"
policy_scope.mkdir()
(policy_scope / "AGENTS.md").write_text(clean_text, encoding="utf-8")
shared_policy = policy_scope / "shared.json"
shared_policy.write_text('{"policy":{"fail_under":90}}', encoding="utf-8")
policy_alias = policy_scope / ".agentconfigscore.json"
policy_links = False
try:
os.symlink("shared.json", policy_alias)
policy_links = True
except OSError:
pass # Some Windows hosts do not permit symbolic links.
if policy_links:
resolved_policy = run([str(python), "-I", "-c",
"from pathlib import Path; from agent_config_score.config import load_policy; "
"assert load_policy(Path.cwd()).fail_under == 90; print('internal policy retained')"
], policy_scope)
require(resolved_policy.strip() == "internal policy retained", "Internal policy alias stopped working")
outside_policy = workspace / "outside-policy.json"
outside_bytes = b'{"policy":{"fail_under":0}}'
outside_policy.write_bytes(outside_bytes)
for target in (outside_policy, Path("missing-policy.json")):
policy_alias.unlink() # Only the disposable fixture-owned alias.
os.symlink(target, policy_alias)
for command in commands:
require(run([*command, ".", "--json", "--fail-under", "0"], policy_scope, expected=2) == "",
"Invalid policy source fell back to defaults or emitted a scan")
broken_policy = json.loads(run([*cli, "doctor", "--json"], policy_scope, expected=1))
require(broken_policy["ok"] is False and any(c["name"] == "config" and c["status"] == "error"
for c in broken_policy["checks"]), "Doctor hid an invalid policy source")
require(run([*cli, "compare", str(base), str(policy_scope), "--json"], repo, expected=2) == "",
"Comparison accepted an invalid candidate policy source")
require(run([*cli, "feedback", "--output", str(protected_report)], policy_scope, expected=2) == "",
"Feedback accepted an invalid policy source")
require(protected_report.read_bytes() == b"preserved report" and policy_alias.is_symlink(),
"Policy source failure overwrote the report or alias")
require(outside_policy.read_bytes() == outside_bytes and not (policy_scope / "missing-policy.json").exists(),
"Policy source validation changed a linked target")
checks.append("policy source boundary: internal aliases retained and invalid aliases rejected where host permits symlinks")

config.write_bytes(b'{"private":"synthetic-\xff"}')
require(run([*cli, ".", "--json"], repo, expected=2) == "", "Invalid config emitted a scan")
config.write_bytes(original[0])
Expand Down
Loading
Loading