Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ jobs:
src/ tests/
scripts/lint_architecture_boundaries.py
scripts/architecture_linter/
scripts/windows_native_symlink_probe_entry.py

- name: Ruff format check
run: >-
uv run --frozen --extra dev ruff format --check
src/ tests/
scripts/lint_architecture_boundaries.py
scripts/architecture_linter/
scripts/windows_native_symlink_probe_entry.py

- name: Check YAML encoding safety
run: |
Expand Down Expand Up @@ -92,7 +94,8 @@ jobs:
--fail-on=R0801 \
src/apm_cli/ \
scripts/lint_architecture_boundaries.py \
scripts/architecture_linter/
scripts/architecture_linter/ \
scripts/windows_native_symlink_probe_entry.py

- name: Lint - auth-protocol boundary (#1212 anti-regression)
run: bash scripts/lint-auth-signals.sh
Expand Down Expand Up @@ -167,6 +170,58 @@ jobs:
git config --list --show-origin
echo "::endgroup::"

windows-native-standard-user-symlink-gate:
name: Windows Native Symlink Acceptance
runs-on: windows-latest
timeout-minutes: 15
permissions:
contents: read

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
env:
GIT_CONFIG_COUNT: '1'
GIT_CONFIG_KEY_0: core.autocrlf
GIT_CONFIG_VALUE_0: 'false'
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0

- name: Install dependencies
run: uv sync --frozen --extra dev

- name: Prove native standard-user fallback
shell: pwsh
timeout-minutes: 10
env:
APM_NATIVE_EXPECTED_HEAD: ${{ github.event.pull_request.head.sha || github.sha }}
run: >-
./scripts/windows_native_standard_user_symlink_gate.ps1
-ExpectedHead $env:APM_NATIVE_EXPECTED_HEAD

- name: Restore owned native fixture
if: always()
shell: pwsh
timeout-minutes: 2
run: ./scripts/windows_native_standard_user_symlink_gate.ps1 -CleanupOnly

- name: Retain native proof and restoration evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-native-symlink-${{ github.run_attempt }}
path: ${{ runner.temp }}/apm-native-symlink-evidence/
if-no-files-found: error
retention-days: 30

windows-git-discovery:
name: Windows Git Discovery (Python ${{ matrix.python-version }})
runs-on: windows-latest
Expand Down
12 changes: 12 additions & 0 deletions docs/src/content/docs/consumer/install-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,18 @@ For the full flag reference, run `apm install --help` or see

## When things go wrong

- **Symlink checkout fallback.** If a checked-out file contains only a short
relative path (for example `../shared/config.yml`) instead of real content,
Git substituted a plain-text fallback for a symlink it could not create.
APM preserves Git's detected `core.symlinks` setting instead of letting an
inherited global value override it; this capability check is not
Windows-specific, but Windows non-admin accounts are the common case
without symlink-creation rights. Explicit command-scope Git settings still
take precedence; APM does not change your global Git configuration. A
successful checkout therefore does not guarantee that a package requiring
real symlinks works -- see "Skipped symlinked agent source" below for the
related install-time check on agent sources. Use a package that ships real
source files, or a symlink-capable environment.
- **Critical security finding.** Install aborts with the offending
characters and file path. Patch upstream when you can; use
`--force` only when you can document the exception.
Expand Down
15 changes: 15 additions & 0 deletions docs/src/content/docs/contributing/integration-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ what the test family you want actually requires.
| `requires_e2e_mode` | Opt-in for the heavyweight golden-scenario suite | `export APM_E2E_TESTS=1` |
| `requires_network_integration` | Opt-in for tests that hit live registries | `export APM_RUN_INTEGRATION_TESTS=1` |
| `requires_windows` | A Windows-only process or filesystem boundary | Run on Windows |
| `requires_windows_native_standard_user` | Verified standard-user token without symlink privilege | The disposable Windows native-symlink CI job; do not enable manually |
| `requires_inference` | Opt-in for tests that call inference APIs | `export APM_RUN_INFERENCE_TESTS=1` |
| `requires_github_token` | A token usable against `github.com` / GitHub Models | `export GITHUB_APM_PAT=...` (or `GITHUB_TOKEN`) |
| `requires_ado_pat` | Azure DevOps PAT for ADO host tests | `export ADO_APM_PAT=...` |
Expand Down Expand Up @@ -456,6 +457,20 @@ fallback; a candidate-count regression checks linear extension scanning.
Only a native Windows run demonstrates Windows behavior; passing mocks or
collection alone do not.

The **Windows Native Symlink Acceptance** job checks out the exact PR head and
uses a temporary standard account on a disposable hosted runner. It requires
successful ordinary file I/O, no administrator or symlink privilege, and actual
Windows error 1314 before running the Git/APM fallback regression. The test keeps
inherited global `core.symlinks=true` while honoring Git's native local `false`;
explicit command-scope intent remains covered separately.

The job requires a passing native case, failure after removing the local
precedence guard in memory, and a restored pass. Empty or skipped runs fail.
Account, profile, scratch and any changed Developer Mode value are cleaned up
independently, with an `always()` cleanup fallback. A restoration failure fails
the job. This is link-text checkout evidence, not archive-symlink support, and
the job does not change repository protection or replace required SDL scanning.

Plugin sequential-install coverage checks deployed-file removal and lockfile
ownership after uninstall, plus unchanged files and ownership for the retained
skill. The local fixture covers both an independent skill and a separately
Expand Down
9 changes: 9 additions & 0 deletions packages/apm-guide/.apm/skills/apm-usage/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ fallback requires an exhausted plan with an executed same-origin effective
HTTPS attempt. Path containment rejects symlink or traversal escapes.
See [GitLab authentication and fetch policy](authentication.md#gitlab-saas-or-self-managed).

### Git symlink checkout fallback

APM preserves Git's detected `core.symlinks` setting and explicit command-scope
overrides without changing global Git configuration, so a successful checkout
is not proof that a package requiring real symlinks works (common on non-admin
Windows accounts). See [Install packages: symlink checkout
fallback](https://microsoft.github.io/apm/consumer/install-packages/#when-things-go-wrong)
for the full behavior and troubleshooting steps.

### Custom git ports

Non-default git ports are preserved on `https://`, `http://`, and `ssh://` URLs
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ markers = [
"requires_ado_bearer: requires az CLI logged in + APM_TEST_ADO_BEARER=1",
"requires_network_integration: requires APM_RUN_INTEGRATION_TESTS=1",
"requires_windows: requires a Windows host",
"requires_windows_native_standard_user: requires the disposable Windows runner's verified non-admin token and symlink denial",
"requires_apm_binary: requires built apm binary on PATH (or APM_BINARY_PATH)",
"requires_runtime_codex: requires codex runtime installed",
"requires_runtime_copilot: requires GitHub Copilot CLI runtime installed",
Expand Down
Loading
Loading