Skip to content

test(public-safety): pin what a compacted value returns over its budget - #4955

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
yuedai-pbc:yuedai/public-safety-text-budget
Sep 23, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
yuedai-pbc:yuedai/public-safety-text-budget

Conversation

@yuedai-pbc

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

  • Goal/source and gap: compact_text() is the budget every public-safety projection spends
    before handing a value to a host, a dashboard or another tool, and loopx/ passes it an
    explicit limit at 202 call sites. No check in tests/ or examples/ ever called it
    with a limit, so the whole over-budget branch — the one that decides what a reader
    actually sees — was unasserted. The owning smoke
    (examples/control_plane/public-safety-readmodel-smoke.py) pins the whitespace, empty and
    local-path refusals at the default limit, and the existing
    tests/control_plane/test_public_safety_path_shapes.py pins the two patterns and
    validate_public_safe_value; neither crosses the budget boundary. Several production
    seams inject a substitute public_safe_compact_text, which means the injected behavior
    was tested while the real one was not.
  • Observable before → after, with the validation row that proves it: before, eight edits to
    the guard passed the whole suite — removing the ellipsis, keeping one character too many,
    tightening the fit test from <= to <, comparing the length before collapsing
    whitespace, dropping the None normalization, moving the default budget, removing the
    credential check, and dropping the rstrip(). After, each fails at least one case
    (regression_parity below). The two disclosures are measured, not inferred.
  • Issue/task and intended base: self-contained coverage gap; no open issue tracks it, and no
    production file changes. Base is current main.

Scope And Continuation

  • Completed scope and remaining work: pinned, all from the caller's side — what survives a
    cut and where the ellipsis goes (one case parametrized over five budgets), that
    whitespace is collapsed before the budget is spent, that a value which exactly fits is
    returned unchanged, that the ellipsis replaces a character rather than adding one, that
    values are coerced and None stays empty, that the default budget is 220, and that the
    credential refusal still fires inside the kept prefix. Deliberately not asserted,
    because both are behaviors this PR would rather see decided than frozen:
    1. limit=0 returns the input unbounded — compact[: limit - 1] becomes a negative
      slice, so a 10-character value with limit=0 comes back 10 characters long; limit=1
      returns only the ellipsis. Every call site in loopx/ passes a literal above 1 today,
      but four seams forward a caller-supplied limit (capabilities/agent_turn_recall/core.py:20,
      capabilities/context_providers/openviking.py:136, capabilities/issue_fix/pr_lifecycle.py:175,
      capabilities/issue_fix/discovered_issue_promotion.py:151), so the bound is a parameter
      rather than a constant. A fail-closed max(limit, 1) or a raised ValueError is the
      owner's choice, and pinning either would foreclose it.
    2. Truncation happens before the redaction check (public_safety.py:137-141), so the
      refusal rules only see the prefix that survives the cut. Measured on current main:
      public_safe_compact_text("token=abcdefghijklmn", limit=8) returns 'token=a…'
      rather than None. The name promises a public-safe value; the guarantee currently
      covers only the part that was kept. Checking before truncating, or widening the checked
      span to the original text, are both behavior changes and neither belongs in a test PR.
  • Slice boundary / successor: complete within this scope. A successor that acts on either
    disclosure should arrive as its own change with the call sites that would be affected.

Validation

  • Tested revision: 522d5179e
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
unit passed python -m pytest -q tests/control_plane/test_public_safety_text_budget.py → 11 passed: 6 new functions, one of them parametrized over five budgets.
unit passed python -m pytest -q tests/control_plane/test_public_safety_path_shapes.py tests/control_plane/test_status_rollout_event_snapshot.py tests/control_plane/test_goal_acceptance_observation.py tests/control_plane/test_monitor_replan_agent_scope.py tests/control_plane/test_public_safety_text_budget.py → 68 passed / 0 failed — every Python test that imports this module or injects a substitute for it, plus python3 examples/control_plane/public-safety-readmodel-smoke.py → exit 0 (the owning smoke still passes alongside the new cases).
static passed python -m ruff check and ruff format --check clean on the new file; python -m mypy (same no-argument invocation as CI) → success in 22 source files; python -m pytest -q tests/canary → 25 passed with the reviewed module findings unchanged.
regression_parity passed Eight mutations of compact_text / public_safe_compact_text, one at a time, reverted before the final run: drop the ellipsis → 9 failed; [: limit - 1]→[: limit] → 9 failed; <= limit→< limit → 1 failed; compare length before collapsing whitespace → 2 failed; str(text or "")→str(text) → 1 failed; default 220→240 → 1 failed; remove the credential check → 1 failed; drop .rstrip() → 1 failed. Reverting all → 11 passed. Failing-before holds by construction: no case in tests/ or examples/ passed a limit to compact_text on the base revision.
unit not_run Full python -m pytest -q tests/ was not completed locally for this revision. The diff adds one test file and changes no module a runtime path imports, so CI's suite on the merge ref is the authoritative run here; this row is left as not run rather than reported as passing.
  • Coverage and gaps: the changed path is one new test file over two functions in
    loopx/control_plane/runtime/public_safety.py; the mutation matrix is the argument that
    the new cases bind that code. Not covered: the two disclosed behaviors above, on purpose.

Frontend / Visual Evidence

  • UI impact: none
  • Before / After / States and viewports shown / Source data: N/A — no surface changed.
  • Attention review: N/A.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

  • Direction / acceptance reference, when applicable: Core control-plane hardening — coverage
    of an already-shipped public-boundary contract. No roadmap id claimed.

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A
  • Provider conformance arms run: N/A
  • Read-only legacy/file/PostgreSQL three-arm rehearsal: N/A — no promotion, runtime-routing
    or compatibility projection changes.

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

202 call sites pass compact_text an explicit limit, but no case in tests/ or examples/ ever crossed the budget, so the over-limit branch -- what a host or dashboard actually reads back -- had no assertion. Pin the kept head, the single ellipsis, the collapse-before-budget ordering, the exact-fit boundary, coercion, the default 220, and that the credential refusal still fires inside the kept prefix.

Two behaviors are disclosed rather than pinned: a limit below 2 stops bounding the value at all, and truncation runs before the redaction check, so a credential past the cut is not refused.

Signed-off-by: Yue Dai <54579099+yuedai-pbc@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

动机

现有 compact_text 被大量公共读模型调用,但测试没有覆盖显式 limit 下的超预算分支。此 PR 只增加这一已交付能力的回归覆盖,不改生产行为;它不是对整个 public-safety 保证的最终验收。

改动思路

直接测试现有 loopx.control_plane.runtime.public_safety 的真实函数,而不是另写 example smoke 或 mock。用多个小预算检查归一化后的截断、单个省略号和精确长度;再检查恰好容纳、None、默认预算及保留前缀内的合成凭据拒绝。测试与已有 path-shape/readmodel 覆盖互补,未新增状态或第二个规则 owner。

具体改动

仅新增 tests/control_plane/test_public_safety_text_budget.py,六个测试函数(其中一个参数化为五种预算),81 行;loopx/、配置、公共文档和 API 均未变化。超预算输出、空值、普通文本及保留前缀内的拒绝各有明确断言。

关键内容讲解

  • 多预算用例断言保留 limit - 1 个字符后只附加一个省略号。
  • 空白折叠、恰好容纳与字符替换用例保护预算计算顺序及边界。
  • 公共安全用例只验证已保留前缀内的拒绝,不声称覆盖完整原始输入的所有风险。

对主干的风险

测试本身不改变公共输出,主要风险是把当前实现的预算形状固定下来;若以后有意更改默认预算或截断形式,需要同步更新明确的调用方契约。PR 正文披露的两个既有边界缺陷仍未修复,不能因这组测试通过就宣布公共安全边界已完整合格;建议由运行时 owner 另行修复并补相应负例。由于这里没有改动运行时,本结论只批准这个独立的测试增量。

我在 exact head 运行了新增及 path-shape 测试(31 passed)、五模块相邻测试(68 passed)、现有 public-safety readmodel smoke、Ruff 与 git diff --check,均通过;未轮询远端 CI。

我的整体评价

这个单文件测试增量有独立、可持续的回归价值,不是与作者同批其他 PR 重复的 walkthrough。它用现有测试 owner 覆盖真实 helper,范围与风险相称;未发现需要本 PR 阻塞的新增回归。未来相关的小型行为保持性重构目前没有必要,优先处理正文已揭示但不属于此 PR 的运行时安全缺口。批准本测试增量,不等于批准当前 public-safety 实现的全部行为。

English verdict: APPROVE - head 522d517; focused budget regression tests are distinct and pass (31 focused, 68 adjacent plus readmodel smoke and static checks). Pre-existing safety gaps remain outside this test-only approval.

@huangruiteng
huangruiteng merged commit 5e30385 into loopx-project:main Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants