Skip to content

Commit 6ac2220

Browse files
authored
fix(runtime): separate execution admission from public disclosure checks (#6981)
* fix(runtime): keep heartbeat launch public scan scope consistent Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> * fix(runtime): preserve held checkpoint scan scope and isolate fixtures Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> * fix(runtime): separate execution admission from public disclosure checks Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> * test(runtime): preserve original held receipt and action fences Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --------- Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
1 parent 79c9615 commit 6ac2220

16 files changed

Lines changed: 398 additions & 95 deletions

‎docs/reference/public-boundary-read-cost.md‎

Lines changed: 79 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,96 @@
22

33
## Contract / 契约
44

5-
`scan_public_boundary` still enumerates the full requested tree, including Git-owned
6-
files inside otherwise pruned directories. Its existing owner decides which
7-
local-private files to exclude **before** submitting any reads. Eligible files are
8-
reopened on every request. The filesystem adapter overlaps at most eight reads and
9-
returns them in the original sorted order; it does not cache scan results or
10-
change leak rules, policy, Git ownership, counts, or error handling.
11-
12-
`scan_public_boundary` 仍扫描完整的指定目录,包括通常会被剪枝、但属于 Git
13-
版本控制的文件。既有扫描 owner 在提交读取之前排除未跟踪的本地私有文件。
14-
每次请求重新打开所有符合条件的文件;文件系统适配器最多并发读取八个文件,
15-
按原来的排序返回结果,不缓存扫描结果,不改变泄漏规则、策略、Git 所有权、
16-
计数及错误处理。
5+
Ordinary `status`, quota decisions, initial planning and native Turn/heartbeat
6+
launch or checkpoint continuation **defer** repository public-boundary scanning.
7+
They still re-read current execution authority: Stop, pause, compute, Goal/Agent,
8+
source, claim/lease and the original receipt/action/settlement binding. Deferral
9+
does not certify public safety. The existing status checks explicitly say
10+
`public boundary scan deferred`; no execution setting or permission bypass is
11+
introduced.
12+
13+
普通 `status`、quota 决策、首次规划、原生 Turn/heartbeat 启动及 checkpoint 接续
14+
默认**推迟**仓库公共边界扫描,仍复查 Stop、暂停、compute、Goal/Agent、source、
15+
claim/lease 和原 receipt/action/settlement 绑定。推迟不等于公开安全;既有状态检查
16+
明确显示 `public boundary scan deferred`,没有新增执行配置或权限绕过。
17+
18+
Explicit `loopx check --scan-path <outgoing-file-or-directory>` and the existing
19+
premerge check scan selected outgoing content. Repeat `--scan-path` for multiple
20+
files, including a saved diff, attachment or draft body. Premerge selects existing
21+
changed files. This is not a universal outbound interceptor: generated-text
22+
presentation guards retain their separate existing owner and policy; arbitrary
23+
uploads/messages are not automatically covered by a successful file check.
24+
25+
显式 `loopx check --scan-path <拟外发文件或目录>` 与既有 premerge 检查选定外发内容。
26+
多个文件可重复传 `--scan-path`,也可选择已保存的 diff、附件或正文草稿;premerge
27+
选择当前存在的变更文件。这不是统一外发拦截器:生成文本的 presentation guard
28+
保留既有独立 owner 和策略,任意上传或消息不因文件检查成功而自动获得覆盖。
29+
30+
The existing scanner retains all rule locations in `hits`. Literal credentials,
31+
private source/path content and non-public registries remain blockers in
32+
`blocking_hits`. A private IP alone is ambiguous: it remains a located warning,
33+
as do existing credential environment/example references. These warnings do not
34+
block the check and are labelled **completed with warnings**, never clean. This
35+
is heuristic assistance, not proof that arbitrary content is safe or secret.
36+
37+
既有扫描器在 `hits` 保留全部规则定位,`blocking_hits` 保留字面凭据、私有来源/
38+
路径内容及非公开 registry 等阻断项。单独内网 IP 属于含糊信号,仍显示带位置的
39+
提示;原有凭据环境变量/示例引用同样提示。提示不阻断检查,显示为
40+
**completed with warnings**,不称 clean。规则是辅助检查,不能证明任意内容安全
41+
或含有秘密。
42+
43+
Missing roots, unreadable directories/files and undecodable UTF-8 fail the check.
44+
Explicit private-file/directory selection cannot hide behind Git ignore or
45+
directory pruning; tracked private state is also blocked. The existing opt-in
46+
tracked-document-URL policy still applies to otherwise public files, and is
47+
re-read on each check; it does not authorize private-state export. Untargeted,
48+
untracked private state stays excluded **before** file reads.
49+
50+
缺失根目录、不可列目录、不可读文件及无法解码的 UTF-8 均使检查失败。显式选择
51+
私有文件/目录不能靠 Git ignore 或目录剪枝得到空扫描成功;已跟踪私有状态也阻断。
52+
原有显式 tracked-document-URL 策略仍适用于其他公开文件,每次检查重新读取;它
53+
不授权私有状态导出。未被选作外发目标的未跟踪私有状态仍在读取**之前**排除。
54+
55+
`scan_public_boundary` enumerates the full selected tree, including Git-owned
56+
files inside otherwise pruned directories. Eligible files are reopened on each
57+
explicit check. The filesystem adapter overlaps at most eight reads and returns
58+
them in sorted order; it caches no content or result.
59+
60+
`scan_public_boundary` 遍历完整指定树,包括通常剪枝目录中的 Git 文件。显式检查
61+
每次重新打开符合条件的文件;文件系统适配器最多并发读取八个文件,按排序返回,
62+
不缓存内容或结果。
1763

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

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

2873
## Placement / 实现边界
2974

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

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

4084
## User paths / 用户入口
4185

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

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

5296
Therefore no packaged frontend rebuild or layout change is required. Existing
5397
status-hot-path regression checks must continue to prove the explicit deferred
@@ -68,6 +112,10 @@ uv run --extra test python -m pytest \
68112
tests/test_contract_public_boundary_prefilter.py \
69113
tests/test_contract_scan_unreadable_files.py \
70114
tests/test_contract_credential_pattern_precision.py \
115+
tests/test_public_check_disclosure.py \
116+
tests/canary/test_premerge_public_boundary_run.py \
117+
tests/control_plane/test_benchmark_current_admission.py \
118+
tests/control_plane/test_benchmark_checkpoint_admission.py \
71119
tests/test_public_package_lock_boundary.py -q
72120
```
73121

‎loopx/canary/premerge.py‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,21 +205,26 @@ def _public_boundary_changed_files_run(
205205
unreadable_directories = [
206206
str(item) for item in boundary.get("unreadable_directories") or []
207207
]
208+
unreadable_files = [str(item) for item in boundary.get("unreadable_files") or []]
209+
missing_roots = [str(item) for item in boundary.get("missing_scan_roots") or []]
210+
warnings = [str(item) for item in boundary.get("warnings") or []]
211+
complete_ok = bool(boundary.get("ok")) and not (
212+
unreadable_directories or unreadable_files or missing_roots
213+
)
214+
label = "completed with warnings" if warnings else "clean"
208215
check.update(
209216
{
210-
"status": (
211-
"passed"
212-
if boundary.get("ok") and not unreadable_directories
213-
else "failed"
214-
),
215-
"ok": bool(boundary.get("ok")) and not unreadable_directories,
217+
"status": "passed" if complete_ok else "failed",
218+
"ok": complete_ok,
216219
"duration_seconds": round(time.monotonic() - started, 3),
217220
"stdout_tail": (
218-
f"public boundary scan clean: {boundary.get('scanned_files')} files"
219-
if boundary.get("ok") and not unreadable_directories
221+
f"public boundary scan {label}: {boundary.get('scanned_files')} files"
222+
if complete_ok
220223
else ""
221224
),
222-
"stderr_tail": "\n".join(hits + unreadable_directories)[-800:],
225+
"stderr_tail": "\n".join(dict.fromkeys(
226+
hits + warnings + unreadable_files + unreadable_directories + missing_roots
227+
))[-800:],
223228
"boundary": {
224229
"scanned_files": boundary.get("scanned_files"),
225230
"skipped_private_state_files": boundary.get("skipped_private_state_files"),

‎loopx/cli_commands/quota_registration.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,13 @@ def register_quota_command(
325325
quota_parser.add_argument(
326326
"--scan-root",
327327
default=default_public_scan_root(),
328-
help="Public files to scan for obvious private material. Defaults to the LoopX install root.",
328+
help="Compatibility scope metadata; quota defers public scans. Use loopx check for outgoing content.",
329329
)
330330
quota_parser.add_argument(
331331
"--scan-path",
332332
action="append",
333333
default=[],
334-
help="Specific public file or directory to scan. Repeatable. Overrides --scan-root when set.",
334+
help="Compatibility scope metadata. Repeatable; does not request a public scan.",
335335
)
336336
quota_parser.add_argument(
337337
"--use-projection-cache",

‎loopx/cli_commands/status_registration.py‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,16 @@ def register_status_commands(
3939
"--scan-root",
4040
default=default_public_scan_root(),
4141
help=(
42-
"Public files to scan for obvious private material. "
43-
"Defaults to the LoopX install root."
42+
"Compatibility scope metadata; operational status defers public scans. "
43+
"Use loopx check for outgoing content."
4444
),
4545
)
4646
status_parser.add_argument(
4747
"--scan-path",
4848
action="append",
4949
default=[],
5050
help=(
51-
"Specific public file or directory to scan. Repeatable. "
52-
"Overrides --scan-root when set."
51+
"Compatibility scope metadata. Repeatable; does not request a public scan."
5352
),
5453
)
5554
status_parser.add_argument("--limit", type=int, default=5)
@@ -143,17 +142,16 @@ def register_status_commands(
143142
"--scan-root",
144143
default=default_public_scan_root(),
145144
help=(
146-
"Public files to scan for obvious private material. "
147-
"Defaults to the LoopX install root."
145+
"Compatibility scope metadata; operational status defers public scans. "
146+
"Use loopx check for outgoing content."
148147
),
149148
)
150149
diagnose_parser.add_argument(
151150
"--scan-path",
152151
action="append",
153152
default=[],
154153
help=(
155-
"Specific public file or directory to scan. Repeatable. "
156-
"Overrides --scan-root when set."
154+
"Compatibility scope metadata. Repeatable; does not request a public scan."
157155
),
158156
)
159157
diagnose_parser.add_argument("--limit", type=int, default=5)
@@ -186,17 +184,16 @@ def register_status_commands(
186184
"--scan-root",
187185
default=default_public_scan_root(),
188186
help=(
189-
"Public files to scan for obvious private material. "
190-
"Defaults to the LoopX install root."
187+
"Compatibility scope metadata; operational status defers public scans. "
188+
"Use loopx check for outgoing content."
191189
),
192190
)
193191
review_packet_parser.add_argument(
194192
"--scan-path",
195193
action="append",
196194
default=[],
197195
help=(
198-
"Specific public file or directory to scan. Repeatable. "
199-
"Overrides --scan-root when set."
196+
"Compatibility scope metadata. Repeatable; does not request a public scan."
200197
),
201198
)
202199
review_packet_parser.add_argument(

‎loopx/cli_commands/turn_decision.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def collect_turn_status_payload(
7878
goal_id=args.goal_id,
7979
available_capabilities=args.available_capabilities,
8080
agent_lane_id=args.agent_id,
81+
include_public_boundary_scan=False,
8182
)
8283

8384

@@ -337,7 +338,9 @@ def current_heartbeat_host_launch_decision(
337338
args = argparse.Namespace(
338339
goal_id=goal_id, agent_id=agent_id, turn_instance_id=original_turn,
339340
todo_id=None, codex_app=False, runtime_profile="generic_cli",
340-
scan_path=[str(project)], scan_root=str(project), limit=0,
341+
# Re-read execution authority, including held receipt identity. Public
342+
# disclosure is independently checked by `loopx check`/premerge.
343+
scan_path=[], scan_root=str(project), limit=0,
341344
available_capabilities=["shell", "filesystem_write"],
342345
)
343346
selection = load_requested_quota_action_selection(

‎loopx/cli_commands/turn_registration.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ def register_turn_commands(
9393
plan.add_argument(
9494
"--scan-root",
9595
default=default_public_scan_root(),
96-
help="Public files to scan for obvious private material.",
96+
help="Compatibility scope metadata; Turn execution defers public scans. Use loopx check before publishing.",
9797
)
9898
plan.add_argument(
9999
"--scan-path",
100100
action="append",
101101
default=[],
102-
help="Specific public file or directory to scan. Repeatable.",
102+
help="Compatibility scope metadata. Repeatable; does not request a public scan.",
103103
)
104104
plan.add_argument("--limit", type=int, default=5)
105105

@@ -153,13 +153,13 @@ def register_turn_commands(
153153
managed_step.add_argument(
154154
"--scan-root",
155155
default=default_public_scan_root(),
156-
help="Public files to scan for obvious private material.",
156+
help="Compatibility scope metadata; Turn execution defers public scans. Use loopx check before publishing.",
157157
)
158158
managed_step.add_argument(
159159
"--scan-path",
160160
action="append",
161161
default=[],
162-
help="Specific public file or directory to scan. Repeatable.",
162+
help="Compatibility scope metadata. Repeatable; does not request a public scan.",
163163
)
164164
managed_step.add_argument("--limit", type=int, default=5)
165165

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

0 commit comments

Comments
 (0)