You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix: enforce FileHelper policy for shared infrastructure - #4951
Migrate project-side filesystem operations owned by install_shared_infra() and refresh_shared_templates() to explicit, root-scoped FileHelper operations, including their shared manifest reads, hashes, recording and saves.
Denied symlink destinations previously warned and silently skipped installation, even with force. They now fail explicitly. Allowed contained targets retain link identity and existing missing-only, managed/unmodified refresh, customization/recovery and force-overwrite decisions. Stale leaf links remain preserved customizations.
The bounded migration adds policy-aware metadata access and explicit per-operation mutation observers, optional helper-backed manifest I/O, and helper handoff from init and integration install/upgrade/switch. Init uses the current requested flag before persistence; other consumers read current project policy. Bootstrap config hierarchy remains deny-only.
Lifecycle changes validate exactly the shared scripts/templates directory scopes and managed .specify/.gitignore file scope through the selected helper before nofollow census. File rollback journals actual contained targets; separate directory bookkeeping removes only identified newly created empty directories, without claiming their contents. Installer/journal containment checks, archive verification, locking, extension/preset registration and integration uninstall remain unchanged. External adapter transactions still reject linked .specify/integrations scope roots.
The baseline includes persisted-policy merge 117fb577fde50d4b787748d24877743367c25aa5. This PR changes 17 files: 11 source, five test files and design/file-helper.md; it does not change template or script content.
Testing
Tested locally with uv run specify --help
Ran existing tests with uv sync && uv run pytest
Tested with a sample project (if applicable)
The existing suite was run with the worktree-owned virtualenv instead of the checklist's generic pytest invocation. Public adapter and real lifecycle tests exercise temporary projects; no additional manual sample-project run was performed.
Command
Result
uv sync --extra test --quiet
Passed; restored the initially missing worktree virtualenv
uv run specify --help
Passed
.venv/bin/python -m pytest tests/specify_cli/test_shared_infra.py -q before production changes, with the initial six deny regressions
Six failures: DID NOT RAISE SymlinkDeniedError, establishing the prior skip-success behavior
.venv/bin/python -m pytest tests/specify_cli/test_shared_infra.py -k real_lifecycle -q before lifecycle fixes
Five failed, three passed; exposed linked scope rejection and leaked new target parents
.venv/bin/python -m pytest tests/specify_cli/integrations/test_installed_adapters.py -k 'shared_helper_allowed_links or current_deny_flag' -q
14 passed
LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/integrations/test_installed_adapters.py tests/specify_cli/integrations/test_command_switch.py tests/specify_cli/test_shared_infra.py -k 'external_init_package_directories or package_post_copy_failure or rejects_symlinked_parent or shared_helper_allowed_links or current_deny_flag or real_lifecycle or directory_' -q
Collection increased from 12,271 to 12,427, with no decrease
LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/test_shared_infra.py tests/test_file_helper.py tests/specify_cli/integrations/test_installed_adapters.py -q -k 'shared_helper_allowed_links or current_deny_flag or real_lifecycle or directory_ or stat or observer or manifest or stale or allowed_links'
Regression coverage includes leaf/parent links, contained/external/dangling/cyclic targets, nofollow metadata, missing/nondirectory parents, callback ordering/failures, overwrite/recovery decisions, manifest formatting and policy errors, stale cleanup, current init flag precedence, and real external-lifecycle rollback restoring bytes/modes and preserving links and unrelated directory contents.
Validation was local macOS. Portable Windows/reparse and newline cases are covered; native Windows filesystem/junction cases were skipped and require CI validation. These checks do not claim race-free filesystem confinement. Earlier unpinned Ruff diagnostics differed from the CI-pinned tool; pinned Ruff passes.
AI Disclosure
I did not use AI assistance for this contribution
I did use AI assistance (fill in the disclosure below)
AI disclosure: GitHub Copilot, powered by GPT-6.1 Sol (gpt-6.1-sol), implemented the code, tests and documentation, ran validation, and authored the commit and PR text on behalf of @mnriem. The agent executed autonomously within user-approved scope and explicit design approval gates, with coordinating-agent technical review; this is not a claim of human line-by-line review. Reasoning effort used the runtime setting and was not explicitly selected in the request.
The refresh caller now restricts modification checks to the destinations it actually selects. Unrelated tracked scripts and other excluded entries are not inspected; their recorded hashes and ownership/recovery records are preserved. The optional manifest query filter leaves default consumers unchanged. FileHelper remains domain-neutral symlink-aware CRUD; this correction changes neither its implementation nor the lifecycle implementation. Template-specific documentation is on the refresh function, not added to the FileHelper contract.
Regression evidence: the pre-fix test failed because refresh inspected an unrelated tracked script. The new matrix covers seven excluded path categories, deny/allow, default/force, and contained/dangling/external/cyclic links, while selected denied paths still fail explicitly.
uvx ruff@0.15.0 check src tests: all checks passed.
.venv/bin/python -m pytest --collect-only -q: 12,543 collected, up 116 from the previous PR head.
git diff --check: passed before commit.
CI/CD was not awaited, as explicitly requested; this comment makes no claim about its result. Validation is local macOS, not native Windows.
AI disclosure: posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol), autonomous execution within user-approved scope and design gates, with runtime reasoning settings not explicitly selected. The agent authored the bounded code/tests/docs correction, ran local validation, and authored the commit and this summary; no human line-by-line review is claimed.
This public-lifecycle regression only covers init and upgrade, so the new persisted-policy handoffs in integration install and integration switch remain unverified. A deny-only switch test cannot catch either command accidentally constructing a deny-mode helper or dropping files; add positive public-path cases showing those commands update a contained linked shared target when the project policy allows symlinks, as required by CONTRIBUTING.md:227-240.
The reason will be displayed to describe this comment to others. Learn more.
Addressed in commit 09ddb195266f9f237f11e577f7ab357cab9adf51. The integration lifecycle document now explicitly distinguishes FileHelper-owned shared infrastructure from the other host write helpers: shared operations honor the selected deny/allow setting, while adapter-package/registry validation, configuration bootstrap, legacy manifest consumers, and uninstall keep their existing restrictions.
This documents the caller behavior already implemented in this PR; it does not change FileHelper's symlink-aware CRUD contract or add ownership, overwrite, rollback, or concurrency responsibilities. No further code change is needed for this finding.
AI disclosure: reply posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol), autonomous execution under the requested scope, with the runtime reasoning setting not explicitly selected. The agent verified the existing documentation correction and authored this reply; no human line-by-line review is claimed.
Addressed the five Windows CI failures in commit d9275bf. The affected tests now read helper-written shared targets explicitly as UTF-8, and the dangling-parent CLI assertion checks the missing target and shared-install failure context rather than Unix-specific error wording. Runtime code and the previously agreed FileHelper/lifecycle behavior are unchanged. The four encoding failures and the error-wording failure were reproduced locally before the fix; all 50 affected parameterized cases then passed with the same simulated Windows defaults. LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/test_shared_infra.py tests/specify_cli/integrations/test_installed_adapters.py -q --tb=short passed all 927 tests, and focused Ruff and diff checks passed. Native Windows execution was not performed locally; CI confirmation remains pending. AI disclosure: posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol), autonomous execution within the user-approved scope, with the runtime reasoning setting not explicitly selected. The agent authored the test-only correction, ran the local checks, and authored the commit and this summary; no human line-by-line review is claimed.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because critical cleanup and switch-failure paths can delete live bundled files or leave the outgoing integration removed without installing its replacement.
Protect current bundled file identities from stale cleanup through allowed parent aliases. Validate the outgoing manifest and finish the initial shared refresh before integration teardown, preserving the existing later setup/fallback recovery behavior.
Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Addressed review 5705232892 in commit 9362408. Both destructive paths described in the review were reproduced: an allowed stale parent alias could delete a still-bundled file, and a shared-path failure during a built-in switch could occur after outgoing integration teardown. Shared cleanup now preserves file identities belonging to current bundled destinations while still removing genuinely obsolete files, including obsolete files with identical bytes. Switch validates the outgoing manifest and completes its initial target shared refresh before teardown, preserving the outgoing files and metadata when that refresh fails. FileHelper is unchanged; later setup/fallback recovery and existing transaction boundaries are unchanged. The existing lifecycle-design clarification remains in place, and the two caller guarantees are now documented explicitly. All 29 new parameterized regression cases failed on the previous PR head and pass with this correction. Local macOS validation passed 284 shared/switch tests, 57 selected external-adapter lifecycle tests, and all 164 legacy integration CLI/manifest tests; focused Ruff and diff checks passed, and 12,610 tests collect. Native Windows confirmation is left to CI. AI disclosure: posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol), autonomous execution within the user-approved scope, with the runtime reasoning setting not explicitly selected. The agent investigated the review, authored the code/tests/docs correction, ran local checks, and authored the commit and this summary; no human line-by-line review is claimed.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because linked-path and overlapping-destination interactions can leave permissions changed after rollback or managed-file manifests inconsistent with disk.
Observe execute-bit adjustments at their actual file targets so lifecycle rollback restores prior permissions, including preserved scripts beneath allowed directory links. Record written shared destinations after the full write phase so overlapping aliases retain hashes for the final bytes without changing write order or ownership decisions.
Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Addressed review 5712164388 in commit ba88c03. Both concerns were reproduced on the reviewed implementation: execute bits on preserved scripts remained changed after rollback, and earlier manifest hashes became stale when later writes reached the same target through an allowed alias. Host execute-bit changes now participate in the existing journal using the actual file target, including preserved user scripts beneath linked shared roots, without adding uninstall ownership. Shared installation and template-only refresh now record written destinations after their full write phase; write order, overwrite/customization decisions, and unselected manifest entries remain unchanged. FileHelper and transaction boundaries are unchanged, and the related design guidance is updated. Regression evidence covers direct and linked permission targets, successful completion and late failure, public external init/upgrade rollback after chmod, overlapping template aliases, and overlapping script/template destinations. Local macOS validation passed 262 shared-infrastructure/public-rollback cases, 57 selected external lifecycle cases, 210 switch/legacy CLI/manifest cases, and three existing extension execute-bit cases; focused Ruff and diff checks passed, and 12,624 tests collect, including 14 new cases. Native Windows confirmation is left to CI. AI disclosure: posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol), autonomous execution within the user-approved scope, with the runtime reasoning setting not explicitly selected. The agent investigated the review, authored the code/tests/docs correction, ran local checks, and authored the commit and this summary; no human line-by-line review is claimed.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because shared symlink targets can cause preserved files to be overwritten without --force, while linked scripts may miss executable-permission repair.
Addressed review 5713833716 in commit 13f469e. Both reported interactions were reproduced, with 11 failing regression cases on the reviewed implementation. Without force, shared installation and template refresh now protect preserved selected file identities from writes through other selected aliases; customized, recovered and untracked targets remain intact while unrelated destinations still refresh. Force retains its existing overwrite behavior, and selected hard-linked entries are conservatively preserved too. Shared-script permission repair now follows allowed contained leaf and parent links, avoids revisiting directory identities, and reports denied, external, dangling, cyclic or unsupported accessed links without modifying them. Init/install/switch/upgrade explicitly pass the operation helper. FileHelper gains only neutral directory-name reads, bounded byte reads and observed regular-file mode updates; path selection, preservation, executable-bit decisions, ownership and rollback remain in callers. Extension-script scanning retains its legacy behavior, and the applicable design guidance is updated. Local macOS validation passed 531 foundation/shared-infrastructure cases, all 698 external-adapter lifecycle cases, 276 integration command/legacy manifest cases, 56 init cases and three extension execute-bit cases; 79 platform-specific cases were skipped. Focused Ruff and diff checks passed; 12,711 tests collect, up 87 from the reviewed head. Native Windows confirmation is left to CI. AI disclosure: posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol), autonomous execution within the user-approved scope, with the runtime reasoning setting not explicitly selected. The agent investigated the review, authored the code/tests/docs correction, ran local checks, and authored the commit and this summary; no human line-by-line review is claimed.
from .integrations._file_changes import changing_file, observe_file_change
files = files if files is not None else project_files(project_path)
This branch has not been deployed
No deployments
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
triage-can-waitVerdict: valid and in-scope but deprioritized; held behind the evidence gate
2 participants
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.
Description
Migrate project-side filesystem operations owned by
install_shared_infra()andrefresh_shared_templates()to explicit, root-scoped FileHelper operations, including their shared manifest reads, hashes, recording and saves.Denied symlink destinations previously warned and silently skipped installation, even with force. They now fail explicitly. Allowed contained targets retain link identity and existing missing-only, managed/unmodified refresh, customization/recovery and force-overwrite decisions. Stale leaf links remain preserved customizations.
The bounded migration adds policy-aware metadata access and explicit per-operation mutation observers, optional helper-backed manifest I/O, and helper handoff from init and integration install/upgrade/switch. Init uses the current requested flag before persistence; other consumers read current project policy. Bootstrap config hierarchy remains deny-only.
Lifecycle changes validate exactly the shared scripts/templates directory scopes and managed
.specify/.gitignorefile scope through the selected helper before nofollow census. File rollback journals actual contained targets; separate directory bookkeeping removes only identified newly created empty directories, without claiming their contents. Installer/journal containment checks, archive verification, locking, extension/preset registration and integration uninstall remain unchanged. External adapter transactions still reject linked.specify/integrationsscope roots.The baseline includes persisted-policy merge
117fb577fde50d4b787748d24877743367c25aa5. This PR changes 17 files: 11 source, five test files anddesign/file-helper.md; it does not change template or script content.Testing
uv run specify --helpuv sync && uv run pytestThe existing suite was run with the worktree-owned virtualenv instead of the checklist's generic pytest invocation. Public adapter and real lifecycle tests exercise temporary projects; no additional manual sample-project run was performed.
uv sync --extra test --quietuv run specify --help.venv/bin/python -m pytest tests/specify_cli/test_shared_infra.py -qbefore production changes, with the initial six deny regressionsDID NOT RAISE SymlinkDeniedError, establishing the prior skip-success behavior.venv/bin/python -m pytest tests/specify_cli/test_shared_infra.py -k real_lifecycle -qbefore lifecycle fixes.venv/bin/python -m pytest tests/specify_cli/test_shared_infra.py tests/test_file_helper.py tests/integrations/test_cli.py tests/test_shared_infra_gitignore.py -q.venv/bin/python -m pytest tests/specify_cli/integrations/test_installed_adapters.py -k 'shared_helper_allowed_links or current_deny_flag' -qLC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/integrations/test_installed_adapters.py tests/specify_cli/integrations/test_command_switch.py tests/specify_cli/test_shared_infra.py -k 'external_init_package_directories or package_post_copy_failure or rejects_symlinked_parent or shared_helper_allowed_links or current_deny_flag or real_lifecycle or directory_' -qLC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -q.venv/bin/python -m pytest --collect-only -qLC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/test_shared_infra.py tests/test_file_helper.py tests/specify_cli/integrations/test_installed_adapters.py -q -k 'shared_helper_allowed_links or current_deny_flag or real_lifecycle or directory_ or stat or observer or manifest or stale or allowed_links'uvx ruff@0.15.0 check src testsgit diff --checkRegression coverage includes leaf/parent links, contained/external/dangling/cyclic targets, nofollow metadata, missing/nondirectory parents, callback ordering/failures, overwrite/recovery decisions, manifest formatting and policy errors, stale cleanup, current init flag precedence, and real external-lifecycle rollback restoring bytes/modes and preserving links and unrelated directory contents.
Validation was local macOS. Portable Windows/reparse and newline cases are covered; native Windows filesystem/junction cases were skipped and require CI validation. These checks do not claim race-free filesystem confinement. Earlier unpinned Ruff diagnostics differed from the CI-pinned tool; pinned Ruff passes.
AI Disclosure
AI disclosure: GitHub Copilot, powered by GPT-6.1 Sol (
gpt-6.1-sol), implemented the code, tests and documentation, ran validation, and authored the commit and PR text on behalf of @mnriem. The agent executed autonomously within user-approved scope and explicit design approval gates, with coordinating-agent technical review; this is not a claim of human line-by-line review. Reasoning effort used the runtime setting and was not explicitly selected in the request.