Repository navigation
refactor(public-safety): decide the credential question at the two capability faces - #5876
Conversation
decision_context packets and material_lifecycle validation each kept their own credential alternation list beside the owner's shape detector, byte-identical to each other, under a comment that called it a "local threshold policy". A list of labels and header shapes is a shape owner, not a threshold. Both faces now make one categorized call to the shared text owner with the named CREDENTIAL_CATEGORIES policy. The owner gains COMPOUND_CREDENTIAL_FIELD_ASSIGNMENT_PATTERN, reached only through the new include_compound_field_assignment opt-in: it is the one spelling the private lists caught and every category arm misses, because those arms anchor the label with a word boundary and underscore is a word character. The opt-in stays off by default so the four migrated text owners and the publication tier change no verdict. Refs loopx-project#5136 directions 1 and 2, and the caller-facing successor loopx-project#5335 recorded. Signed-off-by: Hsuehtan <296098438+Hsuehtan@users.noreply.github.com>
…e call The first version folded the assignment value, which for a construction is the re.compile(...) call rather than its pattern, so the census reported a clean repository even with the deleted lists put back. It also keyed on any three labels, which flagged environment-name and field-name rules that decide a different question. The criterion is now an alternation that reaches for whitespace, three sites that still decide it are declared with the reason each one stays, and the probe rows state the limit out loud. Signed-off-by: Hsuehtan <296098438+Hsuehtan@users.noreply.github.com>
ruff format --check is not a gate in this repository, but a file added here should not add a new violation, so this one is formatted and reports no hunks. Signed-off-by: Hsuehtan <296098438+Hsuehtan@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=xhigh
English verdict: REQUEST_CHANGES — exact head a875f6cdf1bb934ab313f2b4546aeacb572912fd. One independently reproduced P2: compound credential assignments with short or quoted letter-only values now pass both real public packet builders. Existing659Python/7TS and green premerge do not cover this regression. No CI queried or awaited.
动机
生成 Decision Context 或 Material Lifecycle 公开摘要的操作者。
普通摘要里的 api_key 提及原先被误拒,现在可以通过;但带短值的复合凭据赋值原先被拒,当前在两个真实公开 packet builder 中原样通过。
同一独立夹具在不可变基线21项全部通过,当前20项失败1项通过;Decision Context changed_facts.summary 和 Material Lifecycle stop_condition 都放行短凭据赋值。
重复规则确实增加维护成本,正常摘要被关键词误拒也会打断工作;收敛到一个检测 owner 有实际价值。但包含赋值运算符或引号的凭据值与裸词提及不同,不能为了减少误报把它们一起释放。
本次不改变既有路径和 raw URL 拒绝规则,不授予能力执行、公开发送或权限;不是整个安全收口任务的验收,真实模型和完整 packaged 产品路径尚未核验。
当前复合短值赋值的公开过滤退化需在本 PR 修复;其余未迁移 caller 保留在#5136,AST census 的 function/动态表达式盲区未被证明消除。
改动思路
沿用 public_safe_text 检测 owner 与现有 caller 权限策略,复合字段识别默认关闭,仅两个迁移入口启用;不另建 Python/TypeScript 决策源。
两个入口先保留各自空值、长度、路径和 raw URL 检查,再把凭据问题交给一套显式 category/reason。正常裸词改善由新 accepted 表证明,未迁移的四个 text owners 和 TypeScript corpus 不选择新 compound arm。我独立比较 shared classify/matches/publication 三种 API 的 category、reason、pattern 和决策,在五种 category 策略、两种 path-gap 设置上,基线/当前570项完全一致。这支持隔离设计,却不能证明两个启用入口安全。
负向路径的关键区别是 compound key 把 credential label 与下划线或其他字符连在一起,既有单词边界规则看不到它;新 arm 又要求值有数字/base64字符或至少16字母,短 quoted 值便漏过,随后公开 packet builder 原样接收。
具体改动
全量四文件 +449/-39:三个现有 production Python 文件和369行 caller/census 测试,没有新增能力入口或持久化状态。接受依据为 #5136 maintainer frame,不可变引用 maintainer-comment-5857977250,并核对已接受的#5335 owner 分层。Direction 1 implemented:共享 owner/显式策略;Direction 2 not_met:短值不能由偶然长度门槛决定;Direction 3 out_of_scope:本 PR 保留现有 location 规则;Direction 4 not_met:需要补真实 nested/public caller 的短赋值反例,现有表只覆盖强 token 值。
关键代码讲解
COMPOUND_CREDENTIAL_FIELD_ASSIGNMENT_PATTERN(public_safe_text.py:194)识别 compound key,却复用了 shaped token/16字母 opaque run 作为赋值后的门槛。短值、包括引号内六字母值,没有独立的 assignment/quoted signal,因此掉入无匹配分支。_compact_text(decision_context/packets.py:69)删除原 substring 列表,改用CREDENTIAL_CATEGORIES和显式 opt-in。classifier miss 后返回原文,build_decision_evidence_packet的 nested changed_facts.summary 随之带入 public_safe packet。compact_text(material_lifecycle/_validation.py:44)采用相同路径;我使用一个合法 topic 的build_material_explore_intent,确认 constraints.stop_condition 同样原样通过。权限 false 或 visibility 标签不会清洗文本。
[P2] 保留复合凭据字段赋值的明确拒绝边界。 例如合成的 client_secret="hunter"、db_password=x、password_hash='hunter'。这里值只是可公开的测试占位;三类都在基线被拒而当前被接受。这不是裸词改善,也没有 maintainer 接受的 per-field 放宽决定。在现有 canonical compound assignment owner 中显式处理赋值信号和 quoted short 值,不依赖数字或16字母长度;保留非迁移默认和裸词改善,补同一 base/head 两 helper 及两个真实 builder 的负向回归。
对主干的风险
当前独立运行659项仓库 Python 检查(包括本 PR61项)与7项 TS 检查通过,Ruff、types、开发时 advisory 和 full semantic 检查通过;premerge5direct+5selected全部通过。它们与新反例不矛盾:同一21case独立 oracle 在不可变 merge base 全部通过,当前20失败1通过,20失败覆盖两 helpers 各六类和两个实际 builder 各四类,均是未抛 credential ValueError,非 mock 推断。
首次 material 夹具误传空 topics,未进入文本验证;首次 base 夹具导入了新增常量,也未完成 collection。均已更正,只有更正后合法输入、固定类别策略的同一基线/当前结果用于归因。私有活跃 Goal 未被注入故障,provider/model 没有执行。AST census 只覆盖可折叠的 module-level compile,函数/动态拼装存在声明过的盲区,不能当完整安全证明;完整 packaged frontend/Lark、native Windows 和模型长期净成本未验。
我的整体评价
交付方向 justified_increment,当前 REQUEST_CHANGES。long_horizon 和 user_experience 均有具体 regression:裸词体验改善是正向,短凭据赋值进入 public_safe 内容的退化阻止这次合并,不能用整体样本多数通过抵消。当前最小修复留在 shared owner,仍复用既有 caller 策略,恢复明确赋值信号并保留 default-off570项 parity;不需要把三个剩余 caller 或整个安全迁移一次性加入本 PR。bounded future-facing pass 的重复规则收口已应用,但相关 assignment 语义必须在同一主题中修好;不能仅追加更强 prose 或增加一个只镜像 token 输出的测试。
…nments The compound field-name arm reused the owner's value test, so a glued label followed by an operator released values carrying no digit and fifteen letters or fewer: client_secret="hunter", db_password=x, password_hash='hunter' all reached the two public packet builders verbatim, while the identical free-standing spelling was still rejected. An operator beside a credential label already states an assignment, which is what the labelled assignment arm encodes with no value floor, so the compound arm now stops at the operator too. The residual runs the other way and is disclosed in the comment: a field-name suffix also reads a label plural before an operator, so the arm stays opt-in. Pinned with four rows in the glued class, asserted through both helpers and both real builders, and covered by the existing blast-radius test that surfaces which did not choose the opt-in are unchanged.
|
Reproduced and fixed in What was wrong. The compound field-name arm reused the owner's value test, so a credential label glued into a field name still needed either an assembled run or a fifteen-plus-letter run after the operator. That released the class you named into both migrated faces: What changed. Disclosed residual, stated on the constant rather than hidden. Dropping the value floor widens the other direction too: the field-name suffix also reads a label plural sitting in front of an operator, so prose such as Tests. Four rows added to the glued class, so they are asserted through both helpers and through the existing blast-radius test that the surfaces which did not choose the opt-in see no new verdict, and through the test that the arm is the only reason the class is rejected. One new test feeds every row through a legal build of both real builders, not just the helpers. Local gates (same window, two trees).
Both failing sets carry the same 11 ids, and re-running just the three files they come from on both trees gives a byte-identical sorted CI has not run on the new head yet: the runs are still waiting on the maintainer approval gate, so the green above is local, not pipeline. |
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=xhigh
English verdict: APPROVE — exact head 1a62339e857c83691e43f96c54ec73fb98f4aecb. The previous compound short/quoted-assignment blocker is independently resolved in both actual public builders. The same21case oracle passes at immutable base/current;570complete default-off observations are identical. Safe mentions improve, value protection remains. No CI queried or waited, no merge/install/review dismissal, no long-run model savings claim.
动机
生成 Decision Context 或 Material Lifecycle 公开摘要的操作者。
普通摘要中的 api_key 提及和复数 secrets 原先被误拒,当前可以通过;包含短值或引号值的复合凭据赋值仍被拒,两个真实公开 packet builder 的拒绝边界已恢复。
同一独立旧反例在不可变基线和当前版本均为21项通过,上一版20项失败已恢复;未迁移接口的570条完整判定与基线相同,另20条 caller 对照只有4条预期裸词放宽。
重复规则增加维护成本,正常摘要被关键词误拒会反复打断工作。此次收口把少误拒与仍拒绝明确凭据赋值一起交付,实际改善不靠测试数量或规则名称推断。
本次只收口两个现有公开文本入口,不改变路径、raw URL、长度、权限、持久化或模型执行;不是整个#5136迁移或长期跨领域净成本的验收。
剩余未迁移 caller 和 AST 动态构造盲区保留在#5136;这两个实际入口未发现当前 blocker。
改动思路
复用 public_safe_text 检测 owner 与既有 caller 策略,复合字段赋值识别默认关闭,仅两个迁移入口启用;不另建决策源或共享状态。
两入口先保留空值、320字符、本机路径、raw URL与错误信息,再选择 credential/credential_word策略。compound key 加冒号或等号进入赋值识别,不再要求值含数字或达到16字母,短值和引号值在公开 builder发出内容前被拒。普通 api_key提及、复数 secrets可以通过。纯检测没有发送权限,也没有 provider、Goal、调度写入。
具体改动
全量四文件+512/-39:三处现有生产代码与432行 caller/census测试。依据 #5136 maintainer frame,revision maintainer-comment-5857977250。Direction 1 implemented:共享检测/显式策略;Direction 2 implemented:已测试正常提及放宽、短赋值保护保留;Direction 3 out_of_scope:location规则不改变;Direction 4 implemented于这两个入口:实际 nested/public builder、双向接受/拒绝对照和默认隔离均验证,whole迁移未宣布完成。
关键代码讲解
COMPOUND_CREDENTIAL_FIELD_ASSIGNMENT_PATTERN(public_safe_text.py:194)识别字段后的赋值符,而非通过值的数字或长度猜测赋值,上一版的短字母值缺口在既有owner修复。_compact_text(decision_context/packets.py:69)删除本地重复列表,显式category/opt-in;实际build_decision_evidence_packet的 nested changed_facts.summary先校验再输出。compact_text(material_lifecycle/_validation.py:44)使用同一owner;合法单topic的build_material_explore_intent的 stop_condition同样拒绝短/quoted赋值,不依赖下游清洗。
对主干的风险
同一独立旧反例在不可变基线和当前版本均为21项通过,上一版20项失败已恢复;未迁移接口的570条完整判定与基线相同,另20条 caller 对照只有4条预期裸词放宽。
当前613项相关源码Python、7项TS corpus、Ruff与TypeScript typecheck通过;开发advisory在full semantic之前执行,未检出新增carrier不是语义证明。premerge5direct+5selected premerge passed;0failures.覆盖diff、改动compile、维护成本、full semantic与public/private边界,risk_profile和独立boundary额外run未选取,未把它们称作执行。
20条caller对照中,只有两入口各两条bare api_key/复数secrets改变,其他16条的短赋值、quoted值、header、本机路径、URL、空值、长度与错误信息保持。570条off比较含完整category、reason、pattern和publication结果,不能仅以boolean相同证明兼容。
测试只有合成占位和实际确定性builder,没有活跃Goal故障或模型执行。AST census只覆盖有限可折叠module-level regex,函数和动态拼装仍有盲区;exact password/secret/Bearer裸词仍保守,不能泛称所有密码讨论都放宽。compound子串策略可能保守误拒,与基线一致。完整packaged/Lark和长期跨领域净成本未测。
语义与 CI 对齐
复用既有credential category/reason;命名集合和opt-in保留于原owner,没有新共享状态词汇。赋值拒绝是强制校验,census/advisory是辅助证据,destination权限与默认隔离分别核验。当前同一旧反例由上一版20失败恢复到21通过,Direction2/4的本slice缺口已关闭,broader#5136仍保留。
我的整体评价
交付为 justified_increment,long_horizon与user_experience的决策均为 improved。
普通摘要更少被误拒,两份重复凭据规则被删除,且短值赋值保护恢复;局部正向收益有真实入口反例和默认隔离证据,完整产品旅程及长期模型成本未测。
bounded future-facing pass已应用:删除重复知识、相关短赋值修复同owner,无需另建framework。当前exact head未发现未解决finding;历史REQUEST_CHANGES不被自动撤销,按仓库规则由maintainer合并,未升级本机或改变权限。
Goal And Delivered Outcome
#5136(open; the maintainer's four directions of 2026-09-27) andthe successor note
#5335recorded when it merged — "the remaining caller faces … each need anamed policy chosen from their destination".
loopx/capabilities/decision_context/packets.py:50andloopx/capabilities/material_lifecycle/_validation.py:25each kept a credential alternation list,byte-identical to each other, consulted beside the owner's shape detector:
SECRET_LIKE_SURFACE_PATTERN.search(text) or _CREDENTIAL_RE.search(text). The comment above bothlists said "local threshold policy only", but the list holds labels and a header shape — a shape
owner, not a threshold. So two capability faces independently decided which English spellings count
as a credential, and a fix in the shared owner silently left both behind. Consumers affected: every
Decision Context packet field and every Material Lifecycle validation field that goes through
_compact_text/compact_text.password,secretorapi_keywith no value adjacent is no longer refused; every assignment the old lists accepted still is, plus
five spellings they missed and one spelling no owner arm reached before (
db_password = "…").Proven by the
unitrows (both faces driven through their real entrypoints) and theregression_parityrow (named accepted/rejected lists recomputed over a 2,147-input grid).is closed here — directions 1-4 of that issue remain open for the faces this slice does not move.
Author Declaration
mainataa87cc019.Implemented against
loopx/public_safe_text.py; aconsumer consults it instead of keeping its own shapes", "one policy-bearing call, not an OR of
detectors") and direction 2 ("a bare
Bearer,passwordorsecretmention, and prose such as'the Bearer token expired', is not a leak"), plus direction 4 ("report newly accepted / newly
rejected per migrated face", "keep each surface's own wording and length limit"). Also the promise in
tests/control_plane/test_public_safety_credential_shape_owner.py:8-12, which after refactor(public-safety): decide credential shapes in one owner #5135 statesthat "each site keeps only its own threshold policy … and consults the owner for the shapes".
loopx/public_safe_text.pyclassify_private_texttest_no_module_outside_the_owner_keeps_a_credential_alternation_list_compact_text,compact_texttest_both_faces_reject_a_credential_label_glued_into_a_field_nameCATEGORY_CREDENTIAL_WORD,CREDENTIAL_CATEGORIEStest_both_faces_accept_a_bare_mention_with_no_value_beside_itNEWLY_ACCEPTED,NEWLY_REJECTEDunitrow belowcontains a credential-like value,max_lenunchangedtest_benign_prose_still_passes_both_facesLABELED_CREDENTIAL_ASSIGNMENT_PATTERN(named by #5335)file:///~/recognition at these facesfind_public_safe_local_pathstill called unchangedtest_public_safe_text_owner_parityre-run, unchanged verdicts(
_CATEGORIZED_PRIVATE_TEXT_PATTERNS,_SHAPE_DETECTORS) and every named policy(
TEXT_OWNER_CATEGORIES,ARTIFACT_LIFECYCLE_CATEGORIES) before choosing the mechanism; measured theaccepted/rejected delta by running the old union and the new policy over a generated grid rather than
reasoning about it; deliberately left path and URL handling exactly as it was. What is assumed, not
verified: the four migrated text owners' verdicts are held by the corpus they already pin, not
re-derived here.
Scope And Continuation
import reremoved from both files, where the list was theonly user); one categorized call per face against the named
CREDENTIAL_CATEGORIES; a new ownerarm
COMPOUND_CREDENTIAL_FIELD_ASSIGNMENT_PATTERNfor the one spelling the lists reached and everycategory arm misses — a credential label glued into a field name, invisible because the label arms
anchor with
\band_is a word character; a census guard that names any module still decidingthis question for itself.
remote_locationandlocal_pathat these two faces. Each still asksfind_public_safe_local_pathandREMOTE_LOCATION_SURFACE_PATTERNseparately, unchanged.Folding them into the same call would let this slice decide, per face, whether internal-state
text may carry a URL — the caller migration [Architecture]: two modules both claim to own "private-looking text" #5136 is still open for.
say which way; the arm has been named since fix(public-safety): separate credential words from credential values #5335 and is untouched.
extensions/presentation.pyrejects an assignment whose value is one character, which no owner arm reproduces without the
undecided item above;
control_plane/todos/handoff_note.pyalso names vendor forms(
ak/sk,access_key_id) the owner does not;loopx/contract.pyis the publication tier,whose move needs the corpus parity slice rather than two capability tables. Each is declared in
DECLARED_OPEN_SITESwith its reason, and a declaration whose site has already been convertedfails (
M9in the mutation table).benchmark_toolkit/environment_access.py(environment-variable names),control_plane/testing/model_behavior_qualification.py(field names) andregistry.py's privatetext marker list. They decide a name or a marker, not whether free text carries a credential value;
the criterion and its limit are stated in
_is_credential_text_ruleand pinned by probe rows.sites and one owner arm without touching the corpus, the TS mirror or another face. Successor: the
three declared faces, then the direction-2 short-assignment decision that unblocks the strictest one.
Validation
a875f6cdf(3 commits, 4 files, +449 −39);621b1229bproduct,a3ef532c3censusfold fix,
a875f6cdfformatting.unitpython -m pytest -q tests/control_plane/test_public_safety_credential_caller_faces.py-> 61 passed: per-face rejection of the glued-field class, the pinned newly-accepted / newly-rejected lists, the arm's load-bearing check, the census and its five probe rows (both faces driven through_compact_text/compact_text, with an unpatched positive control).unitpython -m pytest -q tests/control_plane -k "public_safety or public_safe or decision_context or material_lifecycle or maintainability"-> 604 passed / 0 failed, 6930 deselected: the owner, the classifier's 4,032-form biconditional, the cross-runtime corpus, the four migrated text owners, and both faces' own suites.integrationpython -m pytest -q tests/architecture tests/canary— head 2 failed / 1444 passed, and the same selection in an unmodified worktree at the base revision 2 failed / 1444 passed, with the two failure ids identical in both (test_top_level_module_budget…= the 148-vs-147 pin reported in #5685,test_source_session_registry_denial…). Both runs used the same interpreter and the repository's qualified Node line.staticpython -m ruff checkclean;python -m mypy->Success: no issues found in 19 source files;loopx check --scan-pathon all four changed paths ->errors=0.regression_parityBEGIN RSA PRIVATE KEYwithout its---fence, which the old substring list caught and the owner's arm requires the fence for). Every row that pairs a label with an operator still rejects. Both lists are pinned as named assertions, so a future re-widening fails a test. Mutations: 10 variants, 9 killed — a face re-adding a private list (1 red), each face dropping the opt-in (7 red each), the new arm anchored (9), the policy droppingcredential_word(3), the arm applied regardless of the opt-in (3, including the classifier's biconditional), a face stop consulting the owner (35), the word arms re-widened to substrings (5), a declared site converted without retiring its declaration (1). 1 variant survives, disclosed as the census's stated limit: an alternation constructed inside a function body rather than at module level, which the fold does not see; a fifth row pins that a word list with no whitespace test is likewise not caught.censuspython examples/semantic-vocabulary-drift-smoke.pymeasured on this head and on an unmodified worktree at the base revision, byte-for-byte the same line: same_runtime_forks=2/2 same_runtime_fork_definitions=5/5 conflicting_values=16/16 conflicting_definitions=54/54 schema_version_same_runtime_forks=0/0 multi_value_twins=8/8 multi_value_forks=2/2 multi_value_forks_semantic=1/1 multi_value_fork_definitions=6/6 same_runtime_forks_semantic=2/2 conflicting_values_semantic=0/0, twins_raw=45, generated_verified=2, independently_maintained=43/43. No registry budget and noBUDGET_ANCHORliteral moves in this PR, and the registry I/O census test inside the compared architecture selection reports the same outcome on both sides.manualruff format --checkis not a gate here, and all three product files already fail it on the unmodified base —loopx/public_safe_text.py5 hunks before / 5 after,packets.py2 / 2,_validation.py1 / 1. The new test file was formatted and reports 0 hunks.patterns, so a face that quietly stops consulting the owner goes red (mutation M7 confirms: 35). The
new arm is covered by 7 pinned spellings × both faces, and by a with/without the flag comparison that
fails if the arm ever stops being the only reason that class rejects. Gaps named plainly: (a) the
local-path and remote-location questions at these faces are unchanged and unmeasured here; (b) the
three declared faces keep their own verdicts, so "one owner for credential text" is still a program
rather than a fact after this PR; (c) the fold covers literal concatenation,
"|".join([...])andmodule-level name chains — an f-string or a function-built pattern is not folded, which is the
surviving mutation; (d) no TS surface is touched, so the two runtimes' behaviour for these two faces
is not a claim here (they are not mirrored in the Vision checkpoint path).
See validation disclosure guidance.
Frontend / Visual Evidence
Type of Change
states is not a leak
directions, quantified in the
regression_parityrowLoopX Area
Technical Direction
directions 1, 2 and 4, continues fix(public-safety): separate credential words from credential values #5335.
Shared-authority RFC fixture impact
N/A — no TypeScript control-plane migration or shared Goal Authority claim.
tests/fixtures/public_safe_text_corpus.json, the TS Vision mirror and the dual-runtime twin inventory are untouchedby this diff; the parity suite was re-run and reports the same verdicts as before.