Skip to content

test(public-safety): pin that a credential key is one key in any spelling - #5070

Open
JunZ-Leo wants to merge 1 commit into
loopx-project:mainfrom
JunZ-Leo:junz/public-safety-field-name-spelling
Open

JunZ-Leo wants to merge 1 commit into
loopx-project:mainfrom
JunZ-Leo:junz/public-safety-field-name-spelling

Conversation

@JunZ-Leo

Copy link
Copy Markdown

Goal And Delivered Outcome

  • Goal/source and gap: normalize_public_safe_field_name is the single decision point in front of exact field-name classification inside validate_public_safe_value, and it had zero references in tests/ while having two production callers (loopx/control_plane/runtime/public_safety.py itself and loopx/capabilities/reliability_diagnostics/envelope.py). Its own docstring states the claim — "Field classification is exact after case, separator, and camelCase normalization" — and nothing held it.

    What that costs concretely: the suffix families (_token, _secret, _password, _credential, _credentials) and the raw / raw_* rule only match because the boundary splits camelCase. reviewToken folds to reviewtoken, which is in no family and ends in none of those suffixes, so renaming a field to camelCase would walk out of the rule untouched. Measured: with the split removed, reviewToken, rotateSecret, viewerPassword, handoffCredential and noteToken all become acceptable names.

  • Observable before → after, with the validation row that proves it: before, no test asserted that accessToken and access_token reach the same verdict; after, 21 assertions do, and five mutations against the production function each fail a distinct subset (see regression_parity). No behaviour changes.

  • Issue/task and intended base: no board item; this is the same shape as test(extensions): pin what a capped provider run returns and stops #4924 and test(turn-driver): pin the host-failure retry ceiling and unmapped policies #4951/test(public-safety): pin what a compacted value returns over its budget #4955. Base main at 27f0fc93b. Related follow-up filed separately as [Bug]: a bytes field name is classified through its repr, so it can escape the public-safety rules #5069, which records a measured hole this PR deliberately does not pin as correct.

Scope And Continuation

  • Completed scope and remaining work: tests only; git diff --stat -- loopx/ is empty. The set covers spelling equivalence for a family member, the suffix names that only the split classifies, the raw rule's camelCase reach, benign keys the boundary must not sweep up, and that the rejection names the caller's own key through nested mappings and lists.

    Not pinned, on purpose: a non-str key escapes the boundary rather than being classified, because the caller folds it with str(key) — measured on current main, b"raw" normalizes to b_raw and is accepted while "raw" is rejected. I did not encode that as expected behaviour, since asserting it would make the hole harder to remove. test(public-safety): pin that a credential key is one key in any spelling #5070 records it with the reproduction and leaves the fix (reject non-str keys, or decode them) to the owner.

  • Slice boundary / successor: complete within this scope. The SECRET_LIKE_SURFACE_PATTERN / LOCAL_PATH_SURFACE_PATTERN pre-normalisation rules are already covered by tests/control_plane/test_public_safety_path_shapes.py, and the compaction budget by tests/control_plane/test_public_safety_text_budget.py; this file adds the naming boundary and nothing else.

Validation

  • Tested revision: c688d5fdb
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
static passed python3 -m ruff check on the new file; ruff format --diff reports no deviation for it; python3 -m mypy without arguments as CI runs it — Success: no issues found in 23 source files (unchanged scope, since no production file moved); loopx check --scan-path on the new file — public boundary scan clean: 1 files; git diff --check clean.
unit passed tests/control_plane/test_public_safety_field_name_spelling.py — 21 passed: 7 spellings of access_token fold to one key and one verdict, with the message asserted equal to p.<caller's own spelling> is a credential-bearing field; 5 suffix-shaped camelCase names; the raw rule over raw, raw_body, rawOutput; 7 benign names that must stay acceptable; and the nested p.outer.reviewToken / list p[0].accessToken paths.
regression_parity passed Six mutations applied one at a time to normalize_public_safe_field_name / _is_credential_field / the raw rule, each reverted before the next: removing the camelCase boundary fails 9; stopping the casefold fails 11; treating a space as part of the token fails 1; dropping the credential suffix tuple fails 6; dropping the raw_ prefix fails 1. One mutation — removing the explicit .strip() — is not caught, and is an equivalent mutation: re.sub(r"[^A-Za-z0-9]+", "_") already folds leading and trailing spaces into underscores and .strip("_") then removes them, so the call cannot change any output. It is recorded rather than dropped. After restoring, this file plus test_public_safety_path_shapes.py and test_public_safety_text_budget.py run 52 passed, 0 failed.
  • Coverage and gaps: the assertions sit at the one place the verdict is decided, and each mutation name corresponds to a distinct clause of the documented rule, so a future edit that weakens any clause is visible rather than silent. Values are covered by the sibling files named above and are not re-tested here.

    Disclosed rather than smoothed: validation used a borrowed interpreter from an existing local virtualenv rather than a fresh uv sync --extra test. This is a test-only change, so no full-suite before/after comparison is claimed; the three public-safety test files were run together as the affected neighbourhood.

…ling

`normalize_public_safe_field_name` is the single decision point in front of
exact field classification, and the suffix families and the `raw` / `raw_*`
rule only exist because it splits camelCase: `reviewToken` folds to
`reviewtoken`, which belongs to no family, so without the boundary renaming a
field to camelCase walks out of the rule untouched. No test held that
equivalence, and the function had no direct reference in `tests/`.

The set pins seven spellings of one family member onto one verdict, five
suffix-shaped names that only the split classifies, the raw rule's camelCase
reach, seven benign camelCase keys the boundary must not sweep up, and that the
rejection names the caller's own key through nested mappings and lists.
Production code is unchanged.

Signed-off-by: JunZ-Leo <100498253+JunZ-Leo@users.noreply.github.com>

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.

1 participant