Skip to content

fix(public-safety): separate credential words from credential values - #5335

Open
sakurahello1 wants to merge 13 commits into
loopx-project:mainfrom
sakurahello1:codex/public-safe-text-word-policy
Open

sakurahello1 wants to merge 13 commits into
loopx-project:mainfrom
sakurahello1:codex/public-safe-text-word-policy

Conversation

@sakurahello1

@sakurahello1 sakurahello1 commented Sep 30, 2026 •

Copy link
Copy Markdown

Goal And Delivered Outcome

  • Outcome basis / optional anchor: Refs [Architecture]: two modules both claim to own "private-looking text" #5136, direction 2 (bare words are not a leak) and direction 4 (per-face strictness). The maintainer's own words on that issue set the bar this implements: "开发代码提 pr 时的 public safe 比 loopx 内部状态的 public safe 要严格一些,后者可以松一些", and direction 2 names Bearer / password / secret and the Bearer token expired as false positives. Direction 1 (refactor(control-plane): single owner for private-text classification #5245) and direction 3 (fix(public-safety): decide the local-path check in one owner #5296) are already merged; this is the half that remains unclaimed in that thread.

  • Goal/source and gap: one 11-pattern tuple in loopx/public_safe_text.py decided both questions at once for the four text owners (feedback, authority, boundary_authority, the TypeScript Vision checkpoint). Two wrong verdicts followed from that, in opposite directions:

    value in an internal-state field before after
    the Bearer token expired rejected accepted
    the password is stored in the vault rejected accepted
    read the secret from the environment rejected accepted
    a raw GitHub token with no label accepted rejected
    a private key block accepted rejected
    token: abc123 (colon form) accepted rejected
    /home/dev/x.json, C:/Operators/... accepted rejected
    password is abc123, secret Qz8m2Xp7Lm, Bearer, aB3d9QkLm, password is + a quoted passphrase, token abc123def rejected at the PR base, released by the previous head of this branch rejected by both tiers
    password=hunter2, secret: env rejected rejected
    Bearer authentication is required, the password is configured per environment rejected accepted by the four owners, still rejected by publication

    The old rule recognized a credential only next to an Authorization: label or as a bare word, so it rejected prose and waved through values. Migration onto classify_private_text gives these owners the shared shape detectors they never had, which is why the loosening and the tightening arrive together.

  • Observable before → after, with the validation row that proves it: the frozen copy of the previous rule still runs against the shared corpus and the delta is asserted as two named lists (seven corpus samples move to "accepted by the internal-state tier", four move to "newly rejected", unit and regression_parity rows below). The released class is now checked as a biconditional over prefix × label × connector × value (4,032 forms) against a predicate written from the contract prose inside the test, not against the module's own patterns: every form the contract calls a value is rejected inside the policy, every form it calls prose is accepted there, and the publication tier differs only by the credential-word class.

  • Review round 2 (cocolord, exact head 6e938bdc1): both blockers are closed here. The internal-state tier no longer releases value-bearing text and no longer rejects ordinary prose, because the single eight-character bearer floor was replaced by four independent signals (assignment punctuation, a connector followed by a digit- or +/=-bearing token, a quoted run, a letter-only run at the named ceiling), mirrored in both runtimes, pinned to a declared corpus, and locked at source level so the connector, the label list and the ceiling cannot drift on one side only. The corpus is now the oracle: each row names the signal that explains its verdict, both suites assert that every signal has a row and every row names a known signal, and no test derives "safe" from "this implementation regex did not match". The five stale project-registry I/O coordinates are regenerated, only the line field changed for those five sites, and the two architecture tests that failed at that head pass at this one.

  • Issue/task and intended base: Refs [Architecture]: two modules both claim to own "private-looking text" #5136 (not Closes: that issue stays open for the per-face URL decision and the short-assignment policy). Base 649826221.

Scope And Continuation

  • Completed scope and remaining work:

    • Done: the bare-word category split; the three Python owners migrated onto the named policy; the TypeScript owner migrated onto the same tier and given the two in-policy shape arms, ported from the same compiled pattern sources rather than retyped, so one corpus yields one verdict in both runtimes.
    • Deliberately not decided here, and said so in code and docs:
      1. URLs. The rule these four owners enforced never rejected an ordinary link, so the internal-state policy excludes remote_location rather than starting to decide it. Choosing per face whether an internal-state field may carry a URL is the caller-migration work [Architecture]: two modules both claim to own "private-looking text" #5136 is still open for. This is a non-change, not a loosening: nothing that was accepted before is rejected now.
      2. token=x short assignments. Direction 2 asks for an explicit, tested policy for that shape and does not say which way. The arm is now named (LABELED_CREDENTIAL_ASSIGNMENT_PATTERN) and tested on both sides, so the decision is visible and one line wide when it is made.
      3. artifact_lifecycle keeps rejecting the words. Its policy is an explicit set, so a new category would have loosened it by absence; it now states ALL_CATEGORIES - {remote_location} and a test pins that the word category is present, so "widen by forgetting to list it" cannot happen again.
    • Successor: the remaining caller faces (the ~17 modules that import the owner for other reasons) each need a named policy chosen from their destination, which is the migration [Architecture]: two modules both claim to own "private-looking text" #5136 describes.
  • Slice boundary / successor: this slice is the two tiers direction 2 already settles, plus the four owners that share them. It is independently reversible: reverting restores one constant and four call sites without touching the corpus or the shape detectors.

Validation

  • Tested revision: ed333a57b (9 commits, 11 files, +1134 -108); round-2 commits 1d5ccc57e (contract + tests + corpus), 0b7aa45b9 (docs), e5f267347 (census lines), ed333a57b (cross-runtime spelling lock).
  • Run state: finished
  • Input classes: public_fixture, synthetic
Check kind Result Public-safe evidence / limitation
unit passed pytest tests/control_plane/test_public_safe_text_classifier.py tests/control_plane/test_public_safe_text_owner_parity.py tests/control_plane/test_public_safe_local_path_owner.py tests/control_plane/test_public_safety_path_shapes.py tests/control_plane/test_public_safety_credential_shape_owner.py tests/control_plane/test_remote_location_shape_owner.py tests/control_plane/test_public_safety_field_name_spelling.py tests/control_plane/test_public_safety_text_budget.py tests/control_plane/test_vision_checkpoint_runtime.py tests/control_plane/test_public_safe_decision_replay.py -> 475 passed in 3.42s; includes the tier, four-signal, delta, 4,032-form biconditional and signal-coverage tests added here.
real_entrypoint passed tests/control_plane/test_public_safe_text_owner_parity.py drives all four owners through their own entrypoints (validate_public_safe_text, build_checkpointed_boundary_authority_entry, build_vision_checkpoint -> the TypeScript effect runtime) over all 40 corpus samples (7 public-safe, 26 private-looking, 7 internal-state prose), so the Vision path is the real owner, not a Python restatement.
integration passed Round 2 re-ran the blast radius from the source: the 52 test files that import or reach a changed owner (`grep -rlE "public_safe_text
static passed python -m ruff check on the changed files: clean. Repository-wide ruff check reports one pre-existing finding in loopx/capabilities/manager_context/roundtrip.py (F401 re), reproduced identically on an unmodified 649826221 worktree, so it is not from this branch. python -m mypy (no arguments, as CI runs it): Success: no issues found in 19 source files. npx tsc --project tsconfig.control-plane.json --noEmit: clean. git diff --check: clean.
static passed node --experimental-strip-types --test tests/control_plane_ts/public_safe_text_corpus.test.ts -> 5 passed; tests/control_plane_ts/vision_checkpoint.test.ts -> 19 passed. The tier-composition test asserts the internal-state list is the publication list minus the three word arms plus the two ported shapes, so a count-only change cannot pass.
regression_parity passed Baseline is a frozen copy of the pre-change 11-pattern rule inside test_public_safe_text_classifier.py, judged over the corpus and the 4,032-form class: newly_released == [bearer_word_in_prose, password_word_in_prose, secret_word_in_prose, rotation_note_names_two_schemes, bearer_before_long_ordinary_word, password_copula_ordinary_word, disclosed_residual_short_letter_value], newly_rejected == [token_space_digit_value, token_assignment_colon, raw_github_token_unlabeled, private_key_block_unlabeled], and every previously rejected form is still rejected by the publication tier. 12/12 mutations caught at this revision, each applied and reverted in this worktree, failing set between 3 and 36 cases: connector value arm deleted; quoted value arm deleted; connector without comma/semicolon/dash; connector without the copula spellings; value token stripped of its digit-or-base64 requirement (back to an 8-char run rule); ceiling set to 1; ceiling set to 40; single tier restored; assignment arm given a value floor; and on the TypeScript side the connector arm deleted, the ceiling literal set to 8, and the word filter neutralized. The three TypeScript mutations are caught by both suites, which is the parity harness executing the real owner rather than a Python restatement. Control round green before and after.
regression_parity passed Base/head control through the four real owners, run over the same 21-case table in an isolated worktree at the PR base 649826221 and at this head: at the base 8 verdicts disagree with the contract -- the seven prose forms are rejected by all four owners, and one value form (token beside a space-separated value) is accepted by all four, in both tiers. At this head 0 disagree. The seven disagreeing prose cases are what direction 2 authorizes; the accepted-value case is the pre-existing publication-tier hole this round also closes.
manual passed loopx check --scan-path on each changed path: public boundary scan clean. This is where the one real defect in an earlier revision of this branch came from and was caught: the explanatory comments in public_safe_text.py wrote a credential label next to a value in plain text, the boundary scan reported those as public_boundary_violation: credential, status then reported two contract errors, and quota spend-slot refused automatic compute -- sixteen settlement and turn-replay cases failed because of a comment. Fixed by prose plus the file's existing assembled-literal convention; the 52-file selection at the revision before that fix is what exposed it, and the focused suites did not. Round 2 found one more instance of the same class in this PR's own fixture: two note strings in tests/fixtures/public_safe_text_corpus.json spelled a credential label next to = and :, which the scan reported as credential; both notes now describe the spelling instead of writing it, and loopx check --scan-path over every changed path reports errors=0.
manual passed Disclosure, not evidence: ruff format --check is not a gate in this repository, and loopx/public_safe_text.py and tests/control_plane/test_public_safe_text_classifier.py were already reported as would reformat on the unmodified base. The remaining would-reformat hunks in those two files are pre-existing lines; every line added here is format-stable, verified by re-running --diff on base and head.
  • CI state at these two revisions, for attribution rather than as evidence of adequacy: the first head of this branch failed typescript-core (1/3) and (3/3), which are not red on main. That was this branch: the digest family's own TypeScript guard (tests/control_plane_ts/content_digest_single_owner.test.ts) requires every new RegExp whose pattern it cannot fold to be declared as a second owner of a value shape, and interpolating the bearer floor into the pattern is exactly such a construction. The quantifier is now a literal with the tie to the constant asserted instead. test-shard (3) and (4) were red on the base commit of this PR as well as on main, so they are upstream-side. At 6e938bdc1 the run reported no failures with four checks still in progress, but the two architecture tests named above failed locally at that head and pass at e5f267347 after the census refresh (pytest tests/architecture/test_project_registry_io_census.py tests/architecture/test_semantic_vocabulary_drift.py -> 123 passed). At ed333a57b the remote checks were still starting when this body was written: DCO/Sign-off, changes, build, dependency-review and postgresql-authority (real server) were queued or running, so remote CI is reported as not yet observed green, and the evidence above is local. python -m ruff check clean, python -m mypy -> Success: no issues found in 19 source files, docs-governance-smoke ok, semantic-vocabulary-drift-smoke ok, loopx canary premerge --from-git-diff --git-diff-base main -> status: passed, selected=17, failures=0.
  • Coverage and gaps: the corpus is the contract both runtimes are pinned to, so the tiers are covered through real owners rather than the helper; the enumerated class covers the released verdicts for the words. Gaps named plainly: (a) the contract's residual is a letter-only value of fifteen characters or fewer written beside the label with no quotes and no assignment operator; it is prose to this owner, it is pinned as disclosed_residual_short_letter_value so the limit is a tested decision, the publication tier still rejects the mention, and quoting or appending one digit moves it into the value class. (b) the three shape detectors are ported to TypeScript, the fourth Python detector (remote_location) is intentionally not, so the two runtimes still differ for a raw URL inside Vision text; recorded above as remaining work instead of being papered over. (c) SECRET_LIKE value forms whose exact spelling is outside the corpus (e.g. an aiza prefix) are exercised by the shared pattern object, not by a per-form case here. (d) No consumer outside the four owners was migrated, so no other face's verdict changed; artifact_lifecycle is pinned against accidental widening by test rather than by review.

See validation disclosure guidance.

Frontend / Visual Evidence

  • UI impact: none
  • Before: N/A
  • After: N/A
  • States and viewports shown: N/A
  • Source data: none
  • Attention review: N/A

Type of Change

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

Behavior changes in both directions are disclosed in the table above rather than hidden under "no functional changes": the internal-state tier accepts three prose forms it used to reject, and rejects value shapes it used to accept.

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Public docs or presentation surface (README, protocols, dashboard)

Technical Direction

Shared-authority RFC fixture impact

N/A — this changes the public-safe text contract, not the TypeScript control-plane migration or shared Goal Authority RFC.

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.
  • 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.

…tier

Refs loopx-project#5136, directions 2 and 4. The four text owners rejected every mention of
the bearer, password and secret words, so an internal-state field carrying
"the Bearer token expired" failed while a raw GitHub token in the same field
passed. Both verdicts came from one 11-pattern rule.

The owner now categorizes the bare words separately from the shapes that carry
a value, and the four owners select a policy that recognizes the words without
rejecting them. Migrating them onto the classifier adds the value-shape
detectors they never had, and the TypeScript owner gains the two in-policy
shape arms ported from the same pattern sources so one corpus yields one
verdict. Ordinary links are excluded from the internal-state policy rather
than newly rejected: the rule these owners enforced never decided a URL, and
that is per-face work still open in loopx-project#5136.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
The shared corpus gains an internal-state bucket for the prose the four owners
now accept, samples for the credential values they newly reject, and a public
URL that stays out of the decision. The classifier test freezes the previous
11-pattern rule and asserts the tier difference as two named lists, and the
released class is enumerated over prefix x word x separator x value rather than
sampled, so a form that slips between the arms fails.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
…per holds

An assignment of the scheme word was the one form still released by adjacency:
"bearer: short" matched neither the value floor nor the label arm. The label arm
now names bearer with the other three words, in both runtimes.

The two module-level comments that still described the pre-split list are
corrected: find_private_text_match is the publication tier rather than a helper
preserved for the four owners, and the derived tuple is no longer the
byte-identical list loopx-project#5245 landed.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
…d source

The repository boundary scan reads loopx/** and the test files as source, and the
explanatory comments plus one parametrize row wrote a credential label next to a
value in plain text. That is what its `public_boundary_violation: credential`
report is for, and it did not stop at the comments: the two hits inside
public_safe_text.py became contract errors, so `status` reported unhealthy
contracts and `quota spend-slot` refused automatic compute. Sixteen settlement
and turn-replay cases failed for a comment.

Rewritten in prose, and the assignment rows in the test now assemble the label
the way the file already assembled every other sensitive literal.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 03:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

CI's TypeScript digest guard requires every `new RegExp` whose pattern it cannot
fold to be declared as a second owner of a value shape. Interpolating
BEARER_VALUE_MIN_LENGTH into the pattern is exactly such a construction, so the
quantifier is written literally and the tie to the constant is asserted instead:
the source must carry `{<constant>,}`, and both sides of the floor are checked
behaviorally. One failure, found by running the whole control-plane suite rather
than only the files this change edits.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>

@cocolord cocolord 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.

评审提交:6e938bdc15deaefdeed576de7bd615a818bb4463。这是 policy-11 whole-PR、exact-head 评审。我检查了 10 个变更文件、#5136 的目标与验收、Python/TypeScript 两套真实 owner、完整远端 checks,并用 PR merge-base 与 exact head 做了独立对照。需求方向有真实收益,但当前实现同时产生 credential-value 漏放、普通 prose 误杀和本 PR 自身引入的 required-check 失败,因此不能批准。

动机

#5136 指出的双 owner 与判定漂移是真问题:内部状态里的 “Bearer/password/secret” 普通描述不应仅凭单词就被拒绝,而带真实值的 credential、私钥和本机路径仍必须 fail closed。把严格 publication tier 与较宽松 internal-state tier 分开,可以减少无意义的 status/quota 阻塞;把此前漏检的 value shape 收回统一 classifier,也能降低公开投影泄漏风险。这是明确的用户与维护收益,不属于无必要扩展。

但验收标准必须是语义结果,而不是正则自身的自洽。当前测试把“未命中 assignment、Bearer-value 和 secret-like 三个实现正则”当成“没有携带 credential value”,所以 972-form 枚举虽然规模很大,仍没有独立证明安全边界。实际 owner 反例表明目标尚未完成。

改动思路

public_safe_text.py 把 bare credential words 拆成 CATEGORY_CREDENTIAL_WORD,并由 TEXT_OWNER_CATEGORIES 排除该类别和 remote-location 类别;feedback、authority、boundary_authority 改为调用带该 policy 的 classify_private_text。TypeScript Vision checkpoint 镜像同一 tier,并新增 Bearer value、labeled assignment、secret-like 和 local-path 形态。共享 corpus、Python classifier tests、四 owner parity test 和 TypeScript corpus test 用来声明两个 runtime 的一致性,文档则披露 internal-state tier 的新默认。

正向路径是:internal-state 文本进入各 owner,classifier 按 category 选择 pattern;普通 credential-word prose 应通过,明确 credential value 应在写入/投影前被拒绝。负向路径应对所有表达 credential value 的常见语法 fail closed,而不能只覆盖 :、= 和“Bearer 后紧跟至少 8 个允许字符”三种拼写。这里的 decision owner 集中到共享 classifier 是合理方向,但当前 policy 先排除了整个 word category,再用有限正则补洞,导致安全语义取决于标点、连接词和相邻单词长度。

具体改动

阻塞问题

  1. [P1] internal-state tier 会放行带值文本,同时仍会误杀普通 Bearer prose。 在 PR merge-base 上,四个真实入口都拒绝 reviewer 构造的 value-bearing 反例;在 exact head 上,feedback、authority、boundary_authority 和真实 TypeScript Vision checkpoint 一致接受三类文本:password 通过 “is” 连接一个短值、secret 后以空格跟一个长 opaque value、以及 Bearer, 后跟 opaque value。相反,Bearer 后接长度不少于 8 的普通英文单词仍会被 BEARER_VALUE_MIN_LENGTH = 8 判成 token。也就是说,这个 head 既没有守住“internal policy is not a credential-storage exemption”,也没有稳定兑现消除 prose false positive 的收益。请先从独立的语义语法表定义哪些表达携带值(至少覆盖连接词、标点、短值和长普通单词),再让 Python/TypeScript 共用或生成同一 contract,并通过四个真实 owner 做 base/head 正负对照。不要再用“没有命中待测实现正则”作为 safe oracle。

  2. [P1] 本 PR 改动行位后没有更新 project-registry I/O manifest,required CI 被自身改红。 authority.py 和 feedback.py 的 import/call-site 行位变化使 5 个既有 registry I/O site metadata 与 checked-in manifest 不一致。merge-base 上 test_checked_in_project_registry_io_manifest_is_current 与 test_semantic_vocabulary_registry_matches_the_code 都通过;exact head 上同两项稳定失败,且 shard 3/4 报告相同 5 个 site。请运行仓库给出的 manifest generator,逐项确认 classification 未变,再重跑这两个 architecture tests 和完整 required CI。

关键代码讲解

  • TEXT_OWNER_CATEGORIES 是行为变化的核心:它从 ALL_CATEGORIES 整体移除 credential_word,因此后续所有安全性都依赖剩余 value-shape regex 的完备性;当前完备性未被独立证明。
  • BEARER_VALUE_SHAPE_PATTERN / TypeScript BEARER_VALUE_SHAPE 用 8 字符长度区分 token 与 prose。这个阈值会把较长普通单词判成值,却无法识别逗号、连接词等真实 value 表达,不是可靠的语义边界。
  • validate_public_safe_text、build_checkpointed_boundary_authority_entry、feedback validator 与 build_vision_checkpoint 都已迁到新 tier,所以错误不是 helper-only:它会覆盖四个实际写入/投影边界。
  • test_tier_delta_is_exactly_the_word_category_over_the_whole_class 的 released assertion只验证样例没有命中同一实现的三个正则;test_adjacency_is_the_documented_limit_of_the_value_arms 甚至明确把逗号后的 opaque value 视为 prose。这是从实现推导预期,不能作为 credential 安全性的独立 oracle。
  • corpus、owner parity 和双 runtime tests 的结构是有价值的;修复后应保留这条共享验证链,但语料预期必须来自独立 contract,并加入上述反例及反方向普通长单词。

对主干的风险

最高风险是 silent false negative:这些 owner 用于可公开的 feedback、authority、boundary authority 和 Vision 状态。一旦带值文本因标点或自然语言连接词绕过 classifier,后续不会有 error/receipt 提醒操作者,风险只能靠下游偶然扫描发现。另一个方向是 false positive:普通长单词仍会触发拒绝,继续制造作者声称要消除的 status/quota 阻塞,用户收益并未闭环。

作者列出的 focused Python suite 确实通过(390 tests),TypeScript core、dashboard、DCO、dependency review 等多项远端 checks 也通过;但这些不能覆盖独立反例。reviewer 的 immutable merge-base/exact-head 对照证明四 owner 行为发生了上述不安全 delta。远端 test shards 3/4 与本地相同命令又证明 manifest drift 是本 PR 引入;shard 1 也包含相关 inventory drift,shard 2 还有未完全归因的并行基线失败,最终 pytest 与 merge-gate 因此失败。修复语义和 generated census 后,需要重新跑 focused classifier/owner parity、TypeScript corpus、两个 architecture tests 与完整 required CI。

语义与 CI 对齐

当前文档承诺 internal-state tier “recognize credential words without rejecting them” 但仍阻止 credential values;exact-head 行为违反这个当前 contract,因此 semantic alignment 是 violated,不是一个可延期的小建议。默认行为变化已经在文档和 tests 中披露,生产文案保持 domain-neutral,也没有把 guidance 误称为机器义务;真正问题是机器强制规则的 false-positive/false-negative 边界不成立。四 owner 与 Python/TypeScript 两套实现必须在同一独立语义 corpus 上同时闭合。

我的整体评价

结论是 REQUEST_CHANGES。目标值得做,集中 classifier、命名 tier 和四 owner parity 也是正确方向;变更规模对安全边界并非天然过大。不过当前实现用有限字符串形态替代了 bare-word denylist,却把“正则没有匹配”误当成“语义上没有值”,所以 whole diff 对长期维护与用户体验都还属于未证明,安全方向则已出现可复现回归。大规模枚举若仍以实现为 oracle,只会增加维护成本,不能增加置信度。

future-facing pass 应聚焦在一个边界:为 credential-value 建立 Python/TypeScript 共用或可生成的明确语义 contract,覆盖标点、连接词、短值及长普通词,再由现有四 owner 消费;不需要新增另一套 owner 或扩大到 #5136 尚未决定的 URL policy。请同时更新 registry I/O manifest 并让 required CI 恢复。新 head 到来后应重新执行 whole-PR exact-head review;本次 review 不授权 merge,也不要求修改作者范围外的功能。

English verdict: REQUEST_CHANGES on exact head 6e938bdc15deaefdeed576de7bd615a818bb4463. The goal is worthwhile and the shared-tier architecture is directionally sound, but independent probes through all four real owners show value-bearing prose becoming accepted while long ordinary Bearer prose can still be rejected. The 972-form test derives “safe” from the implementation regexes, so it does not prove the claimed boundary. This head also introduces five stale project-registry I/O metadata entries; the same two architecture tests pass at the PR merge-base and fail at the exact head. Define an independent credential-value contract, cover both false-negative and false-positive counterexamples in Python and TypeScript owner parity, regenerate/review the manifest, and rerun required CI.

The internal-state tier released text that carries a value while still rejecting
ordinary prose, because the only discriminator between a credential word and a
credential value was a bearer run of eight characters. A password value reached
through the copula "is", a secret followed by an opaque run, a value after a
comma or dash, a quoted passphrase, and a token label beside a space-separated
value all passed the four real owners, while a scheme name plus an ordinary
English word of eight characters or more was rejected.

State the contract as four independent signals and implement each one: an
assignment operator carries whatever follows, with no length condition; a
connector (whitespace, comma, semicolon, dash, or a copula) followed by a token
containing a digit or one of the base64 characters is a value; the same connector
followed by a quoted run is a value; the same connector followed by an unbroken
letter-only run at the named ceiling is a value. Nothing reads a word's length to
decide whether a credential word is present, so the prose direction and the value
direction stop sharing one number. The connector arms omit ':' and '=' because the
assignment arm already carries those spellings: one spelling names one signal
rather than depending on list order. A label-free value such as `token abc123def`
is now rejected in the publication tier too, which is a hole that predates the
tier split.

Expectations stop being derived from the implementation. Every corpus row now
declares the contract signal that explains its verdict, the fixture lists the
signals, and both runtimes check that every signal has a row and every row names a
known one. The tier-delta sweep is replaced by a biconditional over the whole form
class (prefix x label x connector x value, 4,032 shapes) judged against a predicate
written from the contract prose, with each signal pinned to its own arm and the
four real owners driven over the enlarged corpus. The one residual the contract
cannot recognize -- a letter-only run below the ceiling beside the label -- is kept
as a named corpus row so the limit is a decision with a test.

Refs loopx-project#5136, direction 2 and direction 4.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
…esidual

The published contract said a bearer scheme carrying a value of at least eight
characters is what separates prose from a credential. It now names the four
signals, says that no signal reads a word's length to decide whether a credential
word is present, and states the residual instead of leaving it implicit.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
The migrated import and call-site lines in authority.py and feedback.py moved five
census coordinates. Regenerated in place with the repository generator; only the
line field changed for those five sites, with no site, kind, api or classification
edits, which is what the two architecture tests compare.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
…ross runtimes

The corpus already fails when one runtime's verdict drifts, but it reports a
sample id rather than the spelling that moved. The value connector, the
credential label list and the letter-run ceiling are now asserted to be the same
text in both owners, so a one-sided edit names the piece that drifted. This is the
cheapest form of "one contract, two runtimes" that does not require generating a
regular expression from the fixture.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
@sakurahello1

Copy link
Copy Markdown
Author

Follow-up on current head f738ecf:

The connector and quoted-value arms were inconsistent with the stated no-length-floor contract: both required two characters, so a one-character shaped or quoted value could reach all four internal-state owners. The patch removes that unintended floor in both Python and TypeScript, adds the two cases to the shared corpus, and drives them through every real owner.

Validation:

  • uv run --extra test pytest -q tests/control_plane/test_public_safe_text_classifier.py tests/control_plane/test_public_safe_text_owner_parity.py — 216 passed
  • node --no-warnings --experimental-strip-types --test tests/control_plane_ts/public_safe_text_corpus.test.ts — 7 passed

Please re-review this exact head; CI is running again.

Signed-off-by: sakurahello1 <201035361+sakurahello1@users.noreply.github.com>
@sakurahello1

sakurahello1 commented Sep 30, 2026 •

Copy link
Copy Markdown
Author

CI follow-up: test-shard (4) failed only because the broader shared local-path classifier now rejects the fixture before the later repo-relative check. The input remains rejected; the test now asserts the public-safety boundary that actually fires.

Current pushed commit: ee82cab.

Focused validation: 229 Python tests and 7 TypeScript tests passed. A new CI run should be associated with this head.

This branch has not been deployed

No deployments
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.

3 participants