Skip to content
110 changes: 79 additions & 31 deletions docs/reference/public-boundary-read-cost.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,96 @@

## Contract / 契约

`scan_public_boundary` still enumerates the full requested tree, including Git-owned
files inside otherwise pruned directories. Its existing owner decides which
local-private files to exclude **before** submitting any reads. Eligible files are
reopened on every request. The filesystem adapter overlaps at most eight reads and
returns them in the original sorted order; it does not cache scan results or
change leak rules, policy, Git ownership, counts, or error handling.

`scan_public_boundary` 仍扫描完整的指定目录,包括通常会被剪枝、但属于 Git
版本控制的文件。既有扫描 owner 在提交读取之前排除未跟踪的本地私有文件。
每次请求重新打开所有符合条件的文件;文件系统适配器最多并发读取八个文件,
按原来的排序返回结果,不缓存扫描结果,不改变泄漏规则、策略、Git 所有权、
计数及错误处理。
Ordinary `status`, quota decisions, initial planning and native Turn/heartbeat
launch or checkpoint continuation **defer** repository public-boundary scanning.
They still re-read current execution authority: Stop, pause, compute, Goal/Agent,
source, claim/lease and the original receipt/action/settlement binding. Deferral
does not certify public safety. The existing status checks explicitly say
`public boundary scan deferred`; no execution setting or permission bypass is
introduced.

普通 `status`、quota 决策、首次规划、原生 Turn/heartbeat 启动及 checkpoint 接续
默认**推迟**仓库公共边界扫描,仍复查 Stop、暂停、compute、Goal/Agent、source、
claim/lease 和原 receipt/action/settlement 绑定。推迟不等于公开安全;既有状态检查
明确显示 `public boundary scan deferred`,没有新增执行配置或权限绕过。

Explicit `loopx check --scan-path <outgoing-file-or-directory>` and the existing
premerge check scan selected outgoing content. Repeat `--scan-path` for multiple
files, including a saved diff, attachment or draft body. Premerge selects existing
changed files. This is not a universal outbound interceptor: generated-text
presentation guards retain their separate existing owner and policy; arbitrary
uploads/messages are not automatically covered by a successful file check.

显式 `loopx check --scan-path <拟外发文件或目录>` 与既有 premerge 检查选定外发内容。
多个文件可重复传 `--scan-path`,也可选择已保存的 diff、附件或正文草稿;premerge
选择当前存在的变更文件。这不是统一外发拦截器:生成文本的 presentation guard
保留既有独立 owner 和策略,任意上传或消息不因文件检查成功而自动获得覆盖。

The existing scanner retains all rule locations in `hits`. Literal credentials,
private source/path content and non-public registries remain blockers in
`blocking_hits`. A private IP alone is ambiguous: it remains a located warning,
as do existing credential environment/example references. These warnings do not
block the check and are labelled **completed with warnings**, never clean. This
is heuristic assistance, not proof that arbitrary content is safe or secret.

既有扫描器在 `hits` 保留全部规则定位,`blocking_hits` 保留字面凭据、私有来源/
路径内容及非公开 registry 等阻断项。单独内网 IP 属于含糊信号,仍显示带位置的
提示;原有凭据环境变量/示例引用同样提示。提示不阻断检查,显示为
**completed with warnings**,不称 clean。规则是辅助检查,不能证明任意内容安全
或含有秘密。

Missing roots, unreadable directories/files and undecodable UTF-8 fail the check.
Explicit private-file/directory selection cannot hide behind Git ignore or
directory pruning; tracked private state is also blocked. The existing opt-in
tracked-document-URL policy still applies to otherwise public files, and is
re-read on each check; it does not authorize private-state export. Untargeted,
untracked private state stays excluded **before** file reads.

缺失根目录、不可列目录、不可读文件及无法解码的 UTF-8 均使检查失败。显式选择
私有文件/目录不能靠 Git ignore 或目录剪枝得到空扫描成功;已跟踪私有状态也阻断。
原有显式 tracked-document-URL 策略仍适用于其他公开文件,每次检查重新读取;它
不授权私有状态导出。未被选作外发目标的未跟踪私有状态仍在读取**之前**排除。

`scan_public_boundary` enumerates the full selected tree, including Git-owned
files inside otherwise pruned directories. Eligible files are reopened on each
explicit check. The filesystem adapter overlaps at most eight reads and returns
them in sorted order; it caches no content or result.

`scan_public_boundary` 遍历完整指定树,包括通常剪枝目录中的 Git 文件。显式检查
每次重新打开符合条件的文件;文件系统适配器最多并发读取八个文件,按排序返回,
不缓存内容或结果。

The bound is a file-count bound, not a byte-budget or atomic filesystem snapshot.
Unreadable-file observations and Unicode decoding behavior retain their existing
semantics. An unexpected reader failure is not downgraded to a clean scan. Workers
An unreadable or undecodable input is not downgraded to a clean scan. Workers
only read UTF-8 bytes: traversal, policy, classification and state writes are not
delegated to them.

限制针对待处理文件数量,不是字节预算,也不承诺原子文件系统快照。
不可读文件和 Unicode 解码错误保留既有语义;意外读取异常不会被当成扫描通过。
不可读文件、Unicode 解码错误及意外读取异常不会被当成扫描通过。
工作线程只读取 UTF-8 内容,不承担目录遍历、策略判断、分类或状态写入。

## Placement / 实现边界

The change is a Python filesystem adapter beneath the existing Python scan owner.
It does not add a second control-plane decision source. Moving the scan policy or
completion/deadline authority to a new owner is out of scope; the existing
TypeScript transaction and settlement owners remain unchanged. This bounded I/O
repair need not wait for the larger TypeScript migration.
Filesystem classification and explicit check reporting remain in the existing
Python scan/check owner and bounded reader. There is no second policy store or
control-plane decision source. Existing TypeScript admission, transaction,
completion/deadline and settlement owners remain unchanged.

这次改动是既有 Python 扫描 owner 下面的文件系统适配器,不新增平行控制面决策源。
不迁移扫描策略,也不改变完成、时限或结算权威;既有 TypeScript 事务及结算
owner 保持不变。这个有界 I/O 修复不以前置完成整个 TS 重构为条件。
文件系统分类及显式检查报告保留在既有 Python scan/check owner 和有界 reader,
没有第二份策略存储或平行控制面决策源;既有 TypeScript 准入、事务、完成、
时限和结算 owner 不变。

## User paths / 用户入口

CLI checks and quota preparation reuse the same scan and response projection.
There is no new setting, CLI flag, frontend control or Lark command. The existing
frontend status hot path still explicitly defers the repository scan; it must not
be reported as a completed public-boundary check. Managed/Lark consumers keep the
same quota gate rather than a separate optimization-specific result.
CLI checks run the scanner; operational CLI, managed hosts and Lark use the same
status owner with scanning deferred by default. The frontend status path already
defers it. There is no new setting, CLI flag, frontend control or Lark command.
The compact status projection keeps the deferred check text rather than adding
repository scan bodies to every ordinary response.

CLI 检查及 quota 准备继续复用同一扫描和响应投影,没有新增配置、CLI 参数、
前端控件或 Lark 命令。既有前端状态热路径仍明确推迟仓库扫描,不能冒充已完成
公共边界检查。Managed/Lark 消费者使用原有 quota 门禁,不另建优化专用结果。
CLI 显式检查执行扫描,运行用 CLI、managed host 与 Lark 复用既有状态 owner,
默认推迟扫描;前端状态路径此前已推迟。没有新增配置、CLI 参数、前端控件或
Lark 命令。紧凑状态投影保留 deferred 检查文字,不向每次普通响应加入仓库扫描正文。

Therefore no packaged frontend rebuild or layout change is required. Existing
status-hot-path regression checks must continue to prove the explicit deferred
Expand All @@ -68,6 +112,10 @@ uv run --extra test python -m pytest \
tests/test_contract_public_boundary_prefilter.py \
tests/test_contract_scan_unreadable_files.py \
tests/test_contract_credential_pattern_precision.py \
tests/test_public_check_disclosure.py \
tests/canary/test_premerge_public_boundary_run.py \
tests/control_plane/test_benchmark_current_admission.py \
tests/control_plane/test_benchmark_checkpoint_admission.py \
tests/test_public_package_lock_boundary.py -q
```

Expand Down
23 changes: 14 additions & 9 deletions loopx/canary/premerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,21 +205,26 @@ def _public_boundary_changed_files_run(
unreadable_directories = [
str(item) for item in boundary.get("unreadable_directories") or []
]
unreadable_files = [str(item) for item in boundary.get("unreadable_files") or []]
missing_roots = [str(item) for item in boundary.get("missing_scan_roots") or []]
warnings = [str(item) for item in boundary.get("warnings") or []]
complete_ok = bool(boundary.get("ok")) and not (
unreadable_directories or unreadable_files or missing_roots
)
label = "completed with warnings" if warnings else "clean"
check.update(
{
"status": (
"passed"
if boundary.get("ok") and not unreadable_directories
else "failed"
),
"ok": bool(boundary.get("ok")) and not unreadable_directories,
"status": "passed" if complete_ok else "failed",
"ok": complete_ok,
"duration_seconds": round(time.monotonic() - started, 3),
"stdout_tail": (
f"public boundary scan clean: {boundary.get('scanned_files')} files"
if boundary.get("ok") and not unreadable_directories
f"public boundary scan {label}: {boundary.get('scanned_files')} files"
if complete_ok
else ""
),
"stderr_tail": "\n".join(hits + unreadable_directories)[-800:],
"stderr_tail": "\n".join(dict.fromkeys(
hits + warnings + unreadable_files + unreadable_directories + missing_roots
))[-800:],
"boundary": {
"scanned_files": boundary.get("scanned_files"),
"skipped_private_state_files": boundary.get("skipped_private_state_files"),
Expand Down
4 changes: 2 additions & 2 deletions loopx/cli_commands/quota_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,13 +325,13 @@ def register_quota_command(
quota_parser.add_argument(
"--scan-root",
default=default_public_scan_root(),
help="Public files to scan for obvious private material. Defaults to the LoopX install root.",
help="Compatibility scope metadata; quota defers public scans. Use loopx check for outgoing content.",
)
quota_parser.add_argument(
"--scan-path",
action="append",
default=[],
help="Specific public file or directory to scan. Repeatable. Overrides --scan-root when set.",
help="Compatibility scope metadata. Repeatable; does not request a public scan.",
)
quota_parser.add_argument(
"--use-projection-cache",
Expand Down
21 changes: 9 additions & 12 deletions loopx/cli_commands/status_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,16 @@ def register_status_commands(
"--scan-root",
default=default_public_scan_root(),
help=(
"Public files to scan for obvious private material. "
"Defaults to the LoopX install root."
"Compatibility scope metadata; operational status defers public scans. "
"Use loopx check for outgoing content."
),
)
status_parser.add_argument(
"--scan-path",
action="append",
default=[],
help=(
"Specific public file or directory to scan. Repeatable. "
"Overrides --scan-root when set."
"Compatibility scope metadata. Repeatable; does not request a public scan."
),
)
status_parser.add_argument("--limit", type=int, default=5)
Expand Down Expand Up @@ -143,17 +142,16 @@ def register_status_commands(
"--scan-root",
default=default_public_scan_root(),
help=(
"Public files to scan for obvious private material. "
"Defaults to the LoopX install root."
"Compatibility scope metadata; operational status defers public scans. "
"Use loopx check for outgoing content."
),
)
diagnose_parser.add_argument(
"--scan-path",
action="append",
default=[],
help=(
"Specific public file or directory to scan. Repeatable. "
"Overrides --scan-root when set."
"Compatibility scope metadata. Repeatable; does not request a public scan."
),
)
diagnose_parser.add_argument("--limit", type=int, default=5)
Expand Down Expand Up @@ -186,17 +184,16 @@ def register_status_commands(
"--scan-root",
default=default_public_scan_root(),
help=(
"Public files to scan for obvious private material. "
"Defaults to the LoopX install root."
"Compatibility scope metadata; operational status defers public scans. "
"Use loopx check for outgoing content."
),
)
review_packet_parser.add_argument(
"--scan-path",
action="append",
default=[],
help=(
"Specific public file or directory to scan. Repeatable. "
"Overrides --scan-root when set."
"Compatibility scope metadata. Repeatable; does not request a public scan."
),
)
review_packet_parser.add_argument(
Expand Down
5 changes: 4 additions & 1 deletion loopx/cli_commands/turn_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def collect_turn_status_payload(
goal_id=args.goal_id,
available_capabilities=args.available_capabilities,
agent_lane_id=args.agent_id,
include_public_boundary_scan=False,
)


Expand Down Expand Up @@ -337,7 +338,9 @@ def current_heartbeat_host_launch_decision(
args = argparse.Namespace(
goal_id=goal_id, agent_id=agent_id, turn_instance_id=original_turn,
todo_id=None, codex_app=False, runtime_profile="generic_cli",
scan_path=[str(project)], scan_root=str(project), limit=0,
# Re-read execution authority, including held receipt identity. Public
# disclosure is independently checked by `loopx check`/premerge.
scan_path=[], scan_root=str(project), limit=0,
available_capabilities=["shell", "filesystem_write"],
)
selection = load_requested_quota_action_selection(
Expand Down
10 changes: 5 additions & 5 deletions loopx/cli_commands/turn_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ def register_turn_commands(
plan.add_argument(
"--scan-root",
default=default_public_scan_root(),
help="Public files to scan for obvious private material.",
help="Compatibility scope metadata; Turn execution defers public scans. Use loopx check before publishing.",
)
plan.add_argument(
"--scan-path",
action="append",
default=[],
help="Specific public file or directory to scan. Repeatable.",
help="Compatibility scope metadata. Repeatable; does not request a public scan.",
)
plan.add_argument("--limit", type=int, default=5)

Expand Down Expand Up @@ -153,13 +153,13 @@ def register_turn_commands(
managed_step.add_argument(
"--scan-root",
default=default_public_scan_root(),
help="Public files to scan for obvious private material.",
help="Compatibility scope metadata; Turn execution defers public scans. Use loopx check before publishing.",
)
managed_step.add_argument(
"--scan-path",
action="append",
default=[],
help="Specific public file or directory to scan. Repeatable.",
help="Compatibility scope metadata. Repeatable; does not request a public scan.",
)
managed_step.add_argument("--limit", type=int, default=5)

Expand Down Expand Up @@ -366,7 +366,7 @@ def register_turn_commands(
run_once.add_argument(
"--scan-root",
default=default_public_scan_root(),
help="Public files to scan for obvious private material.",
help="Compatibility scope metadata; Turn execution defers public scans. Use loopx check before publishing.",
)
run_once.add_argument("--scan-path", action="append", default=[])
run_once.add_argument("--limit", type=int, default=5)
Expand Down
Loading
Loading