Repository navigation
repo_shape.py: load_yaml's three refusals and policy-unreadable name the file as shown_path does, and validate-pins.py, family.py and shape-doctor.py read either spelling (#1168) - #1274
Draft
brettheap wants to merge 1 commit into
Conversation
…ame the file as `shown_path` does, so a U+202E, an ESC or a line feed in a typed path is spelled, and validate-pins.py, family.py and shape-doctor.py read either spelling (#1168) `load_yaml` named the file with the bare `{path}` in `yaml-unreadable` (twice) and `yaml-unparsable`, so a bidi override, an ESC or a line feed in a `--plan`, `--path-policy`, `--policy`, `--project` or `--root` path reached the terminal raw from `adopt-project.py check` and `plan`, `validate-repository-naming.py`, `bump-leg.py`, `family.py bump`, `update-shape.py check` and `validate-manifest.py`. Each now names it as `shown_path` does: `repr` for a path that is not printable, byte for byte for one that is. `policy-unreadable`, the next line of `NamingPolicy.load`, does the same. Every reader that finds the path at the start of the detail by its text asks both the spelled and the raw prefix, so it reads a `repo_shape.py` from before this change or after it: `validate-pins.py`'s `_load` (a root re-copies its pinned files one row at a time), `family.py`'s member manifest (the swap to `<repo> @ <commit>:project.yaml`), and in `shape-doctor.py` `naming_script_refusal` and the `manifest-kinds` row's `less_the_path`. `link_declarations`' own swap is unchanged and reads as it did. Rows: tests/test_load_yaml_refusals.py. Closes #1168 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideAPIChange
Uses of the APITeach each text-based refusal reader to recognize both the raw and spelled path prefixes, preserving behavior across mixed copy versions.
TestsAdd refusal, tool-output, compatibility, and reader tests covering unsafe and printable paths across current and older copies.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Goal
repo_shape.load_yamlnames the file it refuses asshown_pathnames a path, in all three of its refusals (yaml-unreadabletwice,yaml-unparsable), and so doespolicy-unreadableon the next line ofNamingPolicy.load. A U+202E, an ESC or a line feed in a typed--plan,--path-policy,--policy,--projector--rootpath no longer reaches the terminal raw from any of the six tools that handload_yamlsuch a path (eight runs:adopt-project.pyreads two flags,validate-repository-naming.pytwo). A printable path prints byte for byte as on main. Every reader that finds the path at the start of a refusal's detail by its text keeps working, beside arepo_shape.pyfrom before this change or after it.What held on main (measured)
Probed at
51a9c5f10with a stubghfirst on PATH,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=falseand an empty global git config: a folderpl<U+202E>anholding a file that is not this reader's YAML (a: 1then: b). Each run exits 2 with one line, the U+202E in it raw:adopt-project.py check --plan(andexecute --plan, the samePlan.load);adopt-project.py plan --path-policy;validate-repository-naming.py --policyand--project;bump-leg.py --root(<root>/project.yaml);family.py bump --family-root(<root>/family.yaml);update-shape.py check --root(<root>/contracts/shape-pin.yaml);validate-manifest.py --root, as a root carries it.On this branch the same eight runs say
'<D>/pl\u202ean/<file>'. The rows below hold all eight, with U+202E, an ESC and a line feed in turn, and a printable name.The readers that find the path in the detail by its text, on main:
validate-pins.py's_loadtestsexc.detail.startswith(f"{path}: ");family.py's_read_member_projectswapsstr(manifest_path)for<repo> @ <commit>:project.yaml;repo_shape.link_declarationsswapsstr(path)forshown_path(path). Beyond the issue's list, a grep of every shipped.pyfor a detail read by its prefix found two more, both inshape-doctor.py:naming_script_refusal(detail.startswith(f"{shape / NAMING_COPY}: ")) and themanifest-kindsrow (exc.detail.removeprefix(f"{path}: ")). Measured with onlyrepo_shape.pychanged:validate-pins.pynamed a root that is not printable by its whole spelled path in place ofproject.yaml;family.pynamed the deleted clone in place of the member whenTMPDIRis not printable (probed with an ESC in the clone's folder); the doctor no longer knew its own policy's refusal, and itsmanifest-kindsrow quoted the whole path again. Each is held below.What changed
scripts/repo_shape.py,load_yaml:named = shown_path(path), used at the three sites;NamingPolicy.load'spolicy-unreadable:shown_path(path). Same module, no new import. The words after the path are unchanged.templates/assembly-root/scripts/validate-pins.py,_load: the prefix is looked for raw and as the gate's own_shownspells it (_shownis written in the gate, not imported, under the skew rule), and either is named relative to the root.scripts/family.py,_read_member_project: the swap toshowntakes the spelled path, then the raw one.shape-doctor.py:naming_script_refusalasks both prefixes (policy-wrong-kindstill names the policy raw); themanifest-kindsrow takes the path off with a newless_the_path, in either spelling.repo_shape.link_declarationsis unchanged: itsreplace(str(path), named)finds nothing to swap in a spelled detail and thewhyreads as it did (a row says so).tests/test_load_yaml_refusals.py: the rows below, added to the file that already holdsload_yaml's refusals (scripts/repo_shape.pyload_yamlcatchesOSErrorandYamlErrorbut notUnicodeDecodeError, so a plan or manifest holding a non-UTF-8 byte makesadopt-project.py checkandexecuteexit 1 with a traceback instead ofREFUSED yaml-unreadable#192), not a new file.No refusal code, remediation or word after the path changes, and a printable path is named as before.
README.md,docs/cli.md, the handbook andAGENTS.mdhold none ofcannot be read (,not UTF-8 at byte,yaml-unparsable,yaml-unreadableorpolicy-unreadable; the release notes of v1.0.0, v1.1.1 and v1.2.0 name the codes as the record of those releases, with printable paths, and stay as they are; the 46 files undertests/that hold them are all in the gates below.scripts/repo_shape.pyandtemplates/assembly-root/scripts/validate-pins.pyare shape copies. Every assembly root carries both, and every family holder carriesrepo_shape.py, soupdate-shape.py checkshows each asupstream-changedin every project untilapplyre-copies it and re-pins, as #827 and #919 did.family.pyandshape-doctor.pyrun from a checkout of the standard and are not copied.The version mix
A root re-copies its pinned files one row at a time (
update-shape.py apply --accept-localkeeps a copy), sovalidate-pins.pyandrepo_shape.pymay be of different ages. Rows, each a real run of the root's own gate over a root under a folder holding the character:project.yamltest_the_gate_names_the_file_from_the_root_beside_this_library51a9c5f10, raw)project.yamltest_the_gate_names_the_file_from_the_root_beside_an_older_librarytest_an_older_gate_beside_this_library_names_the_whole_path_spelledproject.yamlThe third order is the one no change here can reach: that gate's own code asks the raw prefix alone. It still prints nothing raw, and it names
project.yamlagain once the gate is re-copied. A printable root is namedproject.yamlin all four. The two older copies are read withgit show 51a9c5f10:..., a skip where a checkout lacks that history (CI checks out withfetch-depth: 0), sotest_the_gates_load_takes_either_prefixalso holds both halves in process with no history.family.pyand the doctor always import their own checkout'srepo_shape.py, so they cannot meet another age; they read both spellings all the same, so no reader of the prefix depends on which library it meets, and their rows run beside each.Two styles in one tool
adopt-project.pyspells the paths it prints itself with its own_shown(backticks, #1115, and #1152's PR #1177 for--plan), andload_yamlnow spells withshown_path(repr, quotes). Measured, for one plan path:adopt-project.py_shownshown_path`/d/pl\u202ean/plan.yaml`'/d/pl\u202ean/plan.yaml'`/d/x\x1b[31m/plan.yaml`'/d/x\x1b[31m/plan.yaml'`/d/a\nb/plan.yaml`'/d/a\nb/plan.yaml'`/d/caf\udce9/plan.yaml`'/d/caf\udce9/plan.yaml'`/d/it's\x1b/plan.yaml`"/d/it's\x1b/plan.yaml"`/d/trail /plan.yaml `/d/trail /plan.yaml/d/plain/plan.yaml/d/plain/plan.yamlDecision:
shown_pathinload_yaml, and the two styles stand. The escapes are the same; only the delimiters differ, and_shown's extra cases (a trailing space, backticks at both ends) are printable, so neither is a hole. A run never shows both for the plan:checkandexecuteover an unparsable plan each print exactly one line, theload_yamlrefusal, and stop (measured).shown_pathis what the other five tools print for the same refusal, and what the three pinned validators' own_shownprint (test_the_pinned_scripts_spell_a_path_as_the_library_doesholds them equal). One spelling foradopt-project.pytoo would mean a spelling parameter on a function in a shape copy, or re-raising atPlan.loadandPathPolicy.load, in a file #1177 is changing next to those lines and 24 other open PRs touch, for a difference of quote marks.Rows, and what they do on main
109 rows in
tests/test_load_yaml_refusals.py: 98 new, and the 11 that were there (#192). Run on main's code: this branch's rows in the detached tree at51a9c5f10, with main's version of each changed file swapped in, each put back with a sha256 check.repo_shape.pyonlyvalidate-pins.pyonlyfamily.pyonlyshape-doctor.pyonlypolicy-unreadable, 3 names (3)manifest-kindsrow beside this library, 3 names (3)57 of the 98 new rows fail on main's code: the 41 above with main's four files, and 16 more that read the path back (the gate 6,
family.py3, the doctor 4 + 3) with main's reader beside this branch's library, the skew this change would otherwise open.The other 41 new rows pass in every column, as do the 11 that were there. They are the controls the brief asks for: a printable path in each refusal (3), in
policy-unreadable(1) and through each tool (8), which prints byte for byte as on main; the four readers beside a library older than #1168 (gate real run 4, gate in process 4,family.py4,manifest-kinds4) and beside this one for a printable path (6);link_declarations(4), which reads as it did; and the doctor findingpolicy-wrong-kindraw (3). Mutants M5, M8, M10, M12, M13 and M14 each fail some of them; thelink_declarationsrows guard only that itswhydid not move, which nothing in this change can move (M16). The doctor's two line-feed rows fail on main's four files too: a raw line feed splits the naming script's line before the doctor reads it.Mutants
Each applied alone in the detached tree, the 109 rows run, the file put back and its sha256 checked against the branch's (all 17 matched). 15 killed, 2 survived.
cannot be readback to{path}absentrows (3)not UTF-8 at byteback to{path}not-utf-8rows (3)yaml-unparsableback to{path}unparsablerows (3), the 24 tool rows that are not printable, the older gate's (3) and the doctor's line-feedload-yamlrowpolicy-unreadableback to{path}policy-unreadablerows, and the doctor's line-feed rowvalidate-pins.py: the raw half of the prefix test dropped_loadwith an older library (3)validate-pins.py: the spelled half dropped_loadwith this library (3)family.py: the spelled swap droppedfamily.py: the raw swap droppednaming_script_refusal: the spelled prefix droppedload-yamlandnot-a-mappingrows that are not printable (6)naming_script_refusal: the raw prefix droppedpolicy-wrong-kindnamed raw, U+202E and ESC (2)less_the_path: the spelled prefix droppedmanifest-kindswith this library (3)less_the_path: the raw prefix droppedmanifest-kindswith an older library (3)load_yaml'snamedalwaysrepr, a printable path toostr(refusal).startswith(f"REFUSED yaml-unreadable: {path}"), and printable rows of the readerspolicy-unreadablealwaysreprnot-a-mappingrow (2)validate-pins.py's spelled half asrepr(str(path))in place of_shown(str(path))link_declarations'replace(str(path), named)removedshown_path's spelling, so the swap finds nothing to swap; the rowtest_a_link_manifest_is_named_in_why_as_it_wasis there to say the output did not moveless_the_pathreturns the detail wholemanifest-kinds, both libraries (6) and its printable rows (2)Runs
Every run through the lane's runner: a stub
ghfirst on PATH (and the suite's ownghguard ahead of it),GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/null, a blank git identity, git-filter-repo 2.47.0 (CI's pin;--versionsaysa40bce548d2c) on PATH, Python 3.12.3 (CI runs 3.14; no row asserts text that depends on the version).The gate list, 53 files, 4591 tests: the new rows; the hygiene five (
test_path_guard_hygiene,test_repo_hygiene,test_agents_md_codes,test_cli_reference,test_pinned_copy_text) andtest_no_auto_maintenance; the 46 files undertests/that holdcannot be read (,not UTF-8 at byte,yaml-unparsableoryaml-unreadable; everyvalidate-pinsfile (test_pin_validator,test_validate_pins_lockstep_lines,test_validate_pins_without_gitbeside those the grep found); and for the doctor andfamily.pyreaders,test_shape_doctor,test_doctor_validator_verdicts,test_naming_policy,test_family_py_unreadableandtest_validate_family_unreadable. One run of more than 100 tests at a time.0e5f95db5e39b5d7e3test_no_auto_maintenancee39b5d7e3tests/test_load_yaml_refusals.py51a9c5f10+ this changee39b5d7e3differs from0e5f95db5by one comment intests/test_load_yaml_refusals.py(the commit was amended before it was pushed), so the 2.43.0 gate list stands for it, with the small run above on the final head. The 12 skips on 2.43.0: the reftable backend needs git 2.45 (7), the session'sGIT_CONFIG_GLOBAL(2), a PowerShell line printed only on Windows (3). On 2.47.1, 5: the last two kinds.Merges
git merge-tree --write-tree(git 2.47.1) of the heade39b5d7e3with main and with the head of each open PR on a file this changes, main fetched and heads read withgh pr view N --json headRefOidat about 20:30Z:77c56dde651a9c5f10, inscripts/family.pytoo (#1117's remedies, not_read_member_project); the predicted merge, checked out alone, passes the new rows,test_pinned_copy_textand main's touchedfamily.pytests (test_family_add_member_id,test_family_bump_to_checks,test_remove_add_remedies_family_root) withtest_family_py_unreadable: 336 passed on git 2.43.0scripts/repo_shape.py)09697e9c5scripts/repo_shape.py,adopt-project.py)8162b874eshown_pathas it isscripts/repo_shape.py)3146c3ca5--plan's_shown)5208e874evalidate-pins.py; #1248, #1172 alsoshape-doctor.py)9ef8f0ab5,1b747f7ab,fff421456,bbe5b414eshape-doctor.py)c792615f4scripts/family.py)24b6a0af6scripts/family.py)43d2e8b26docs/handbook.htmlconflicts, which this does not touch; against main alone it conflicts there and in five more filesscripts/family.py)74096eb47tests/test_path_guard_hygiene.pyconflicts, which this does not touch; it conflicts there against main alone tooLimits
validate-pins.pybeside thisrepo_shape.pynames a root that is not printable by its whole spelled path, notproject.yaml, until the gate is re-copied (the version-mix table). Nothing raw.policy-wrong-kind, the line afterpolicy-unreadableinNamingPolicy.load, still names the policy raw: the brief kept the scope to the threeload_yamlrefusals andpolicy-unreadable. It is the same one-line change, and the doctor already reads either spelling.plan-unreadable(adopt-project.py, which adopt-project.py: the--planpath incheckandexecute, and the--local-remote-dirpath inexecute'sbare, leg andsplitlines, are named with_shown, as #1154 names--outand--work-dir, so a bidi control, an escape, a line feed or a byte that is not UTF-8 in either no longer reaches the terminal as it was typed (#1152) #1177 spells),path-policy-unreadableandpath-policy-wrong-kind(scripts/path_classify.py),bump-leg-manifest-unreadable(bump-leg.py),manifest-unreadable(validate-repository-naming.py, which validate-repository-naming.py: a name that holds a line feed or another control byte is printed raw, in the table and innaming-unclassified, so the finding forAtlasplus a line feed reads like one for a validAtlas#1267 and siblings.py and validate-repository-naming.py: the layout table, the plain listing and--explain's family rows pad a name by the columns a terminal gives it as stdout writes it, so a wide or combining name's next column starts under its heading (#1216, #1217) #1236 are changing),manifest-missingandmanifest-unreadable(validate-manifest.py),update-pin-unreadable(update-shape.py) and the doctor's ownpolicy-raised. Follow-up candidates.bootstrap.py,siblings.py,validate-family.pyandshape_advisory.py. By reading, each handsload_yaml<root>/<one set name>; where it does not swallow the refusal (two ofsiblings.py's three do), it prints the detail whole (bootstrap.pythroughspelled, which leaves a spelled path as it is), so each now says the spelled path.link_declarations' swap now finds nothing to swap. It is left in place: removing it is a further change to a shape copy for no change in output (mutant M16).Closes #1168
Lane: openreposhape-5 (openRepoShape-5)
🤖 Generated with Claude Code
Summary by Sourcery
Sanitize YAML refusal paths and make all dependent readers compatible with both current and older path spellings.
Bug Fixes:
Enhancements:
Tests: